2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) J - Shopping

2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) J - Shopping

七月 30, 2021

题目概述

$n$个数的数列 $a_n$​ ,$q$次询问。

每次询问给出数$v$,区间端点$[l,r]$,问用$v$按顺序依次对数列$[l,r]$中的数取模,得到的最终答案。

数据范围

分析与解

这道题在考场上试图通过主席树+二分来解,可惜复杂度为$O(qlog^21e18log^2n)$常数大得离谱。故失败。

首先我们注意到一点,就是每一次取模,这个$v$都会减小一半,因此直接取模最多$log1e18$​求解是可行的。关键在于每次精确的找到对哪个数取模?实际上,就是求对于当前这个数,在它右边最近的小于等于它的数的位置。

这里之前用主席树做麻烦了,其实把上面这句话翻译下,不就是该数$x$向右延伸最多能延伸多少遇到的都是$>x$​​的数?这件事就转化为一个区间最小值的问题了。而且为了更好的解决延伸,其实用ST表和倍增思路就一步到位了。

实现见代码。

复杂度为$O(qlogn \cdot log10^{18})$

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL mread() {
LL ret=0,f=1;
char c=getchar();
while(c<'0' || c>'9') {
if(c=='-')f=-1;
c=getchar();
}
do {
ret=ret*10+c-'0';
c=getchar();
} while('0'<=c && c<='9');
return ret*f;
}
#define MAXN 200100
LL mmin[MAXN][25];
LL a[MAXN];
int n,q;
int main() {
LL v;
int l,r,now;
n=mread(),q=mread();
for(int i=1; i<=n; i++) {
a[i]=mread();
mmin[i][0]=a[i];
}
for(int i=1; i<=25; i++) {
for(int j=1; j+(1<<i)-1<=n; j++)
mmin[j][i]=min(mmin[j][i-1],mmin[j+(1<<(i-1))][i-1]);
}
while(q--) {
v=mread(),l=mread(),r=mread();
now=l;
while(now<=r) {
v=v%a[now];
for(int i=25; i>=0; i--)
if(now+(1<<i)-1<=r && mmin[now][i]>v)
now+=((1<<i)-1);//应落到可控范围内
now++;
}
printf("%lld\n",v);
}
return 0;
}