【题目 背景】
小奇总是在数学课上思考奇怪的问题。
【问题描述】
给定一个长度为 n 的数列, 以及 m 次询问, 每次给出三个数 l, r 和 P, 询问 (a[l' ] + a[l' +1] + . . . + a[r' ] ) mod P 的最小值。 其中 l <= l' <= r' <= r。 即模意义下的区间子串和最小值。
【输入格式】
第一行包含两个正整数 n 和 m, 表示数列的长度和询问的个数。 第二行为 n 个整数, 为 a[1] . . a[n] 。 接下来 m 行, 每行三个数 l, r 和 P, 代表一次询问。
【输出格式】
对于每次询问, 输出一行一个整数表示要求的结果
【样例输入】
4 2
8 15 9 9
1 3 10
1 4 17
【样例输出】
21
【数据范围】
对于 20%的数据 n<=100, m<=100, p<=200
对于 40%的数据 n<=200, m<=1000, p<=500
对于 70%的数据 n<=100000, m<=10000, p<=200
对于 100%的数据 n<=500000, m<=10000, p<=500, 1<=a[i] <=10^9
题解(这是老师发下来的)
对于20%的数据,最暴力的做法,在区间内枚举子串,暴力求和取模,最后得出最小值,复杂度m*n^3
对于40%的数据,算法1的区间求和改用前缀和相减,复杂度m*n^2
接下来,思考一下抽屉原理,如果询问的区间长度大等p的话,一定有两个前缀和相减等于0,那么直接输出0即可
对于70%的数据,把所有长度大等p的区间特判,其余暴力,复杂度m*p^2
考虑生日悖论,数列如果是随机生成的,找到2个前缀和相同的就返回0,期望复杂度为m*p
由于第10组数据是随机生成的,所以算法3可以通过8个测试点
对于100%的数据,使用平衡树来寻找某个数的前驱,复杂度为m*p*logp(可能高于noip难度),需要手写treap,splay或替罪羊树等。set由于常数较大,实测和暴力结果一样。
(下面是我自己的话)
首先下面这一句话十分有用:
接下来,思考一下抽屉原理,如果询问的区间长度大等p的话,一定有两个前缀和相减等于0,那么直接输出0即可
但如果你没法理解,可以自己举几个例子直观感知一下(小提示,往余数上想想)
所以对于每一个询问区间我们都 Insert 出一个平衡树 就是把之前记录的前缀和 从 s[l]-s[l-1] ~ s[r]-s[l-1],每一段前缀和都插入,然后边插入一遍查询此时这一段前缀和在平衡树里的前继是谁,此时的前继与这一段前缀和的差值一定是最小的。对于每一段都这样查询,就可以得到这个区间在模P意义下的最小字段和了。
具体细节请看代码
1 #include<iostream>
2 #include<cstdio>
3 #include<cstdlib>
4 #include<cstring>
5 #include<algorithm>
6
7 #define For(i,a,b) for(register int i=a;i<=b;++i)
8 #define Dwn(i,a,b) for(register int i=a;i>=b;--i)
9 #define Re register
10 #define Pn putchar('
')
11 #define llg long long
12 using namespace std;
13 const int N=5e5+10;
14 int n,m,p;
15 int ch[N][2],val[N],cnt[N],fa[N],BH=0;
16 int a[N],root=0,l,r,fn;
17 llg s[N];
18 inline void read(int &v){
19 v=0;
20 char c=getchar();
21 while(c<'0'||c>'9')c=getchar();
22 while(c>='0'&&c<='9')v=v*10+c-'0',c=getchar();
23 }
24
25 void write(int x){
26 if(x>9)write(x/10);
27 int xx=x%10;
28 putchar(xx+'0');
29 }
30 void clear(int x){
31 ch[x][0]=ch[x][1]=fa[x]=cnt[x]=val[x]=0;
32 }
33 bool Wson(int x){
34 return ch[fa[x]][1]==x;
35 }
36 void rotate(int x){
37 int y=fa[x];
38 int z=fa[y];
39 int ws=Wson(x);
40
41 if(z)ch[z][Wson(y)]=x;
42 fa[x]=z;
43
44 ch[y][ws]=ch[x][ws^1];
45 fa[ch[y][ws]]=y;
46
47 ch[x][ws^1]=y;
48 fa[y]=x;
49 }
50
51 void Splay(int x,int goal){
52 while(fa[x]!=goal){
53 int y=fa[x];
54 if(fa[y]!=goal){
55 if(Wson(x)==Wson(y))rotate(y);
56 else rotate(x);
57 }
58 rotate(x);
59 }
60 if(goal==0)root=x;
61 }
62
63 void Insert(int dt){
64 if(root==0){
65 int now=++BH;
66 root=now;
67 ch[now][0]=ch[now][1]=0;
68 val[now]=dt; fa[now]=0; cnt[now]=1;
69 return;
70 }
71 int now=root,pa=0;
72 while(1){
73 if(val[now]==dt){
74 cnt[now]++; Splay(now,0); return;
75 }
76 pa=now;
77 now=ch[now][val[now]<dt];
78 if(!now){
79 now=++BH;
80 ch[now][1]=ch[now][0]=0;
81 val[now]=dt; fa[now]=pa; cnt[now]=1;
82 ch[pa][val[pa]<dt]=now;
83 Splay(now,0); return;
84 }
85 }
86 }
87
88 int Pre(){
89 int now=root;
90 if(cnt[now]>1)return val[now];
91 now=ch[now][0];
92 while(ch[now][1])now=ch[now][1];
93 return val[now];
94 }
95
96 int main(){
97 // freopen("seq.in","r",stdin);
98 // freopen("seq.out","w",stdout);
99 read(n); read(m);
100 For(i,1,n) read(a[i]);
101 For(i,1,n)s[i]=s[i-1]+a[i];
102 For(i,1,m){
103 read(l); read(r); read(p);
104 root=BH=0; fn=2147483600;
105 Insert(0);
106 if(r-l+1>=p){
107 putchar('0'); Pn;
108 continue;
109 }
110 For(j,l,r){
111 int tmp=(s[j]-s[l-1])%p;
112 Insert(tmp);
113 int px=Pre();
114 fn=min(fn,tmp-px);
115 if(fn==0)break;
116 }
117 write(fn); Pn;
118 }
119 fclose(stdin); fclose(stdout);
120 return 0;
121 }