[Ynoi2015]盼君勿忘
题目大意:
给定一个序列,每次查询一个区间\([l,r]\)中所有子序列分别去重后的和\(\bmod p\)(每次询问模数不同)。
解题思路:
在太阳西斜的这个世界里,置身天上之森。等这场战争结束之后,不归之人与望眼欲穿的众人, 人人本着正义之名,长存不灭的过去、逐渐消逝的未来。我回来了,纵使日薄西山,即便看不到未来,此时此刻的光辉,盼君勿忘。————世界上最幸福的女孩
珂朵莉,要永远幸福哦。
---
我们考虑每个数的贡献。即该区间内含有这个数的子序列个数。用补集转化为不含这个数的子序列个数。
那么,假设这个数在\([l,r]\)内出现了\(k\)次,则一共有\(2^{r-l+1}-2^{r-l+1-k}\)个子序列**包含**这个数。
所以,我们莫队,维护所有出现\(k\)次的不同数的和,然后包含这些数的子序列个数都相同,所以直接用和来计算贡献即可。
时间复杂度\(O(nm)\),非常优秀。
考虑根号内分类讨论。
对于出现次数大于\(\sqrt n\)的数,这种数最多不到\(\sqrt n\)个。
所以我们莫队,统计出现次数小于等于\(\sqrt n\)的所有出现次数为\(i\)的和\(s_i\),然后用一个东西来维护所有出现次数大于\(\sqrt n\)的数。
这样的话,每次询问的复杂度就是\(O(\sqrt n)\)了。
看上去好像没什么问题,实际上你会发现求2的幂次还有个\(\log\),而且模数会改,不好预处理。
有一种分块\(O(\sqrt n)\)预处理,\(O(1)\)求幂次的方法,具体就是求出\(2^0,2^1,2^2,\dots,2^{{\sqrt n}-1}\)和\(2^{\sqrt n},2^{2\sqrt n},\dots,2^n\)(模意义),然后每个幂都分成两部分相乘即可。
每次询问重新计算一遍即可。
然后我们考虑用什么东西来维护出现次数大于\(\sqrt n\)的数。我用了unordered_set,理论上是线性的(当然这个理论有多可靠就不知道了)。
这样总时间复杂度\(O((n+m)\sqrt n)\),空间复杂度\(O(n)\)。
C++ Code:
#include<cstdio> #include<cctype> #include<algorithm> #include<vector> #include<unordered_set> #ifdef ONLINE_JUDGE struct istream{ char buf[23333333],*s; inline istream(){ buf[fread(s=buf,1,23333330,stdin)]='\n'; fclose(stdin); } inline istream&operator>>(int&d){ d=0; for(;!isdigit(*s);++s); while(isdigit(*s)) d=(d<<3)+(d<<1)+(*s++^'0'); return*this; } }cin; struct ostream{ char buf[8000005],*s; inline ostream(){s=buf;} inline ostream&operator<<(int d){ if(!d){ *s++='0'; }else{ static int w; for(w=1;w<=d;w*=10); for(;w/=10;d%=w)*s++=d/w^'0'; } return*this; } inline ostream&operator<<(const char&c){*s++=c;return*this;} inline void flush(){ fwrite(buf,1,s-buf,stdout); s=buf; } inline~ostream(){flush();} }cout; #else #include<iostream> using std::cin; using std::cout; #endif #define siz 317 #define N 100005 int n,m,a[N],buc[N],_2[siz+2],__2[siz+2],out[N]; long long bvc[siz+2]; std::vector<int>lr; std::unordered_set<int>s; inline int pow2(int b,const int&md){ int ret=1,a=2; for(;b;b>>=1,a=1LL*a*a%md) if(b&1)ret=1LL*ret*a%md; return ret; } inline int pw2(int m,const int&md){ return 1LL*_2[m%siz]*__2[m/siz]%md; } struct que{ int l,r,id,md; inline bool operator<(const que&rhs)const{ return(l/siz!=rhs.l/siz)?(l<rhs.l):(r<rhs.r); } }q[N]; inline void add(int pos){ const int val=a[pos]; if(buc[val]>siz)++buc[val];else if(buc[val]==siz)bvc[buc[val]++]-=lr[val],s.insert(val);else bvc[buc[val]]-=lr[val],bvc[++buc[val]]+=lr[val]; } inline void del(int pos){ const int val=a[pos]; if(buc[val]>siz+1)--buc[val];else if(buc[val]==siz+1)s.erase(val),bvc[--buc[val]]+=lr[val];else bvc[buc[val]]-=lr[val],bvc[--buc[val]]+=lr[val]; } int main(){ cin>>n>>m; for(int i=1;i<=n;lr.push_back(a[i++]))cin>>a[i]; std::sort(lr.begin(),lr.end()); for(int i=1;i<=n;++i)a[i]=lower_bound(lr.begin(),lr.end(),a[i])-lr.begin(); for(int i=1;i<=m;++i) cin>>q[q[i].id=i].l>>q[i].r>>q[i].md; std::sort(q+1,q+m+1); int l=1,r=0; for(int i=1;i<=n;++i)*bvc+=a[i]; *_2=*__2=1; for(int i=1;i<=m;++i){ while(r<q[i].r)add(++r); while(l>q[i].l)add(--l); while(r>q[i].r)del(r--); while(l<q[i].l)del(l++); const int md=q[i].md,len=r-l+1; for(int j=1;j<siz;++j) _2[j]=2LL*_2[j-1]%md; __2[1]=pow2(siz,md); for(int j=2;j<=siz;++j) __2[j]=__2[1]*1LL*__2[j-1]%md; int&ans=out[q[i].id]; const int all=pw2(len,md); for(int j=1;j<=siz;++j) (ans+=1LL*(all-pw2(len-j,md)+md)*bvc[j]%md-md)+=ans>>31&md; for(int j:s){ (ans+=1LL*(all-pw2(len-buc[j],md)+md)*lr[j]%md-md)+=ans>>31&md; } } for(int i=1;i<=m;++i)cout<<out[i]<<'\n'; return 0; }