bzoj P5016[Snoi2017]一个简单的询问——solution
Description
给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问读入l1,r1,l2,r2,需输出
get(l,r,x)表示计算区间[l,r]中,数字x出现了多少次。
Input
第一行,一个数字N,表示序列长度。
第二行,N个数字,表示a1~aN
第三行,一个数字Q,表示询问个数。
第4~Q+3行,每行四个数字l1,r1,l2,r2,表示询问。
N,Q≤50000
N1≤ai≤N
1≤l1≤r1≤N
1≤l2≤r2≤N
注意:答案有可能超过int的最大值
-by bzoj
http://www.lydsy.com/JudgeOnline/problem.php?id=5016
一开始看错了题,以为是每次询问一个x,
然后想直接主席树;
然后很快发现题不会这么简单;
询问两个区间对应颜色出现次数相乘再求和;
两个区间,颜色;
想到离线
把一个有两个区间的询问变成两个有一个区间和一个前缀的询问作差,
然后按前缀端点排序,逐渐右移端点,维护更长的前缀,以更新询问,
这样每次前缀的端点右移时,前缀里只有一个颜色的数量增加了1,
相应的,以后要询问的区间只有这个颜色对答案的贡献增加了区间中这个颜色的个数,
然而这个颜色对答案的贡献取决于询问的区间中有多少这个颜色,
换言之取决于询问哪个区间,
一开始想用什么主席树之类的维护,死活不会,
然后一看时限3S想到分块暴力,
然后发现很科学;
然后就有了这个做法
把询问(l1,r1,l2,r2)拆成(l1,r1,pos=l2-1,-),(l1,r1,pos=r2,+)
表示
询问[l1,r1]与l2-1前缀的结果,并在原询问中减去它
询问[l1,r1]与r2前缀的结果,并在原询问中加上它
将拆成来的询问按pos从小到大排序,
按这样的顺序处理询问可以通过单向右移端点来依次维护所有询问的前缀
查询的时候,采用分块,
被整块查询的块可以直接使用一个块在当前前缀下的答案
维护方法是
在读入数列时对每个块建一个颜色桶MP[]
每右移前缀端点,使颜色col(x)个数在前缀中增加时,把每个块的答案都增加MP[col(x)]
被查询的散点,每个散点对答案的贡献是当前前缀中与这个散点颜色相同的点的个数
可以开个桶,在前缀变长时逐渐更新即可,
复杂度为$O((N+M)\sqrt{N})$
代码:
1 #include<cmath> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define LL long long 6 using namespace std; 7 int sz,N,Q,num; 8 int a[50010]; 9 LL MAP[233][50010],inlineMAP[50010]; 10 LL ANS[233]; 11 struct ss{ 12 int L,R,pos,id; 13 LL flag; 14 }qrr[100010]; 15 LL ans[50010]; 16 bool cmp(ss a,ss b){ 17 return a.pos<b.pos; 18 } 19 LL get(int ,int ); 20 int main() 21 { 22 int i,j,k; 23 scanf("%d",&N); 24 sz=sqrt(N); 25 for(i=num=1;i<=N;i+=sz,num++){ 26 for(j=i;j<i+sz&&j<=N;j++){ 27 scanf("%d",&a[j]); 28 MAP[num][a[j]]++; 29 } 30 } 31 num--; 32 scanf("%d",&Q); 33 for(i=1;i<=Q;i++){ 34 scanf("%d%d%d%d",&qrr[i].L,&qrr[i].R,&qrr[i].pos,&qrr[i+Q].pos); 35 qrr[i+Q].L=qrr[i].L,qrr[i+Q].R=qrr[i].R; 36 qrr[i+Q].id=qrr[i].id=i; 37 qrr[i].flag=-1,qrr[i+Q].flag=1; 38 qrr[i].pos--; 39 } 40 sort(qrr+1,qrr+Q+Q+1,cmp); 41 j=0; 42 for(i=1;i<=Q<<1;i++){ 43 while(j<qrr[i].pos){ 44 j++; 45 inlineMAP[a[j]]++; 46 for(k=1;k<=num;k++) 47 ANS[k]+=MAP[k][a[j]]; 48 } 49 ans[qrr[i].id]+=qrr[i].flag*get(qrr[i].L,qrr[i].R); 50 } 51 for(i=1;i<=Q;i++) 52 printf("%lld\n",ans[i]); 53 return 0; 54 } 55 LL get(int L,int R){ 56 LL ret=0; 57 int i,j,k; 58 int l,r; 59 if(R-L+1<=sz){ 60 for(i=L;i<=R;i++) 61 ret+=inlineMAP[a[i]]; 62 return ret; 63 } 64 for(i=j=1;i<L;i+=sz,j++); 65 l=i-1; 66 for(;i+sz<=R+1&&i<=N;j++,i+=sz) 67 ret+=ANS[j]; 68 r=i; 69 for(i=L;i<=l;i++) 70 ret+=inlineMAP[a[i]]; 71 for(i=r;i<=R;i++) 72 ret+=inlineMAP[a[i]]; 73 return ret; 74 }
或许有树套树的做法?
Just close your eyes, you`ll be alright, no one can hurt you after you die.