正题
题目链接:https://www.luogu.com.cn/problem/P5072
题目大意
nnn个数,mmm个询问(l,r,p)(l,r,p)(l,r,p)表示询问[l,r][l,r][l,r]的所有子序列的和模ppp。
解题思路
一个出现了kkk次的数会产生贡献2r−l+1−2r−l+1−k2^{r-l+1}-2^{r-l+1-k}2r−l+1−2r−l+1−k次贡献。那么我们设ziz_izi表示出现了iii次的数字和。这样可以O(nm)O(nm)O(nm)统计答案。
考虑根号分治,我们把出现小于n\sqrt nn的数用桶来统计,次数大于n\sqrt nn的数用setsetset维护,这样的数最多只会有n\sqrt nn个。
考虑如何预处理2i%p2^i\% p2i%p,我们可以先预处理20,21,22,...2n,22n...2^0,2^1,2^2,...2^{\sqrt n},2^{2\sqrt n}...20,21,22,...2n,22n...的值,然后询问时拆成两部分即可,这样的时间复杂度也是O(nn)O(n\sqrt n)O(nn)的。
所以时间复杂度为O(nn)O(n\sqrt n)O(nn)
codecodecode
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,siz=317;
struct node{int l,r,p,id;
}q[N];
int n,m,T,a[N],ans[N],b[N];
int z[N],v[N],q2[N],p2[N];
set<int> s;
bool cmp(node x,node y){if(x.l/T==y.l/T)return x.r<y.r;return x.l<y.l;
}
void add(int x){z[v[x]]-=b[x];if(v[x]==siz)s.insert(x);v[x]++;z[v[x]]+=b[x];return;
}
void del(int x){z[v[x]]-=b[x];v[x]--;if(v[x]==siz)s.erase(x);z[v[x]]+=b[x];return;
}
int power(int x,int p)
{return 1ll*q2[x/siz]*p2[x%siz]%p;}
int main()
{scanf("%d%d",&n,&m);T=sqrt(n);for(int i=1;i<=n;i++){scanf("%d",&a[i]);b[i]=a[i];}sort(b+1,b+1+n);int cnt=unique(b+1,b+1+n)-b-1;for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+1+cnt,a[i])-b;for(int i=1;i<=m;i++)scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].p),q[i].id=i;sort(q+1,q+1+m,cmp);int l=1,r=0;set<int>::iterator it;for(int x=1;x<=m;x++){while(r<q[x].r)add(a[++r]);while(l>q[x].l)add(a[--l]);while(r>q[x].r)del(a[r--]);while(l<q[x].l)del(a[l++]);p2[0]=1;for(int i=1;i<=siz;i++)p2[i]=p2[i-1]*2%q[x].p;q2[1]=p2[siz];q2[0]=1;for(int i=2;i<=siz;i++)q2[i]=1ll*q2[i-1]*q2[1]%q[x].p;int pw=power(r-l+1,q[x].p),answer=0,P=q[x].p;for(int i=1;i<=siz;i++)answer=(answer+1ll*(pw-power(r-l+1-i,P)+P)%P*z[i]%P)%P;for(it=s.begin();it!=s.end();it++)answer=(answer+1ll*(pw-power(r-l+1-v[*it],P)+P)%P*b[*it]%P)%P;ans[q[x].id]=answer;}for(int i=1;i<=m;i++)printf("%d\n",ans[i]);return 0;
}