[题解]CQOI2008 T1 位统计 bit
位统计 bit
给出N个[0,65535]的整数,编程支持以下操作:
修改操作:C d,所有数增加d。如果超过65535,把结果模65536。0 <= d <= 65535
查询操作:Q i,统计有多少整数的第i位非0,换句话说,有多少个整数与2^i的“按位与”操作值为正。0 <= i <= 15
输出所有查询操作的统计值。
【输入】
第一行为两个正整数N和M,即整数的个数和操作的个数,第二行包含N
个[0,65535]的整数。以下M行为各操作,格式如题所述。
【输出】
输出M个数,即所有Q操作的统计值。
【样例】
bit.in
8 10
1 2 4 8 16 32 64 128
Q 0
Q 1
Q 2
Q 3
Q 4
Q 5
Q 6
Q 7
Q 8
Q 9
bit.out
1
1
1
1
1
1
1
1
0
0
【数据规模】
数据 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
N | 3 | 10 | 100 | 10^3 | 10^4 | 2*10^4 | 5*10^4 | 10^5 | 10^5 | 10^5 |
M | 3 | 10 | 100 | 10^3 | 10^4 | 2*10^4 | 5*10^4 | 5*10^4 | 10^5 | 2*10^5 |
------------------------------------------------------------------------------------------------------------------------------
【题解】
我首先想的是每次的‘Q’操作就把每个数扫一遍,复杂度为O(m*n),然后看了看数据范围,n<=10^5,m<=2*10^5,显然超时。我就分析了一下复杂度,首先读入m个操作至少有O(m)的复杂度,这个不能降,显然每次操作只能接受常数之间或者是log的复杂度。对于区间操作,要在常数时间完成修改和查询操作显然不可能,所以我们考虑复杂度为log级别的算法。
对于‘C’操作,多个‘C’操作显然可以合并成一个,把数值加起来就行了。‘Q’操作我最初的想法是直接跟一个数&一下,然后发现涉及到进位的问题,显然不能加了之后再来查询(原因上一段已经说了),所以考虑什么时候会涉及到进位。
现在划一下例子:比如我们现在要查询从右向左数第五位是1的数,之前‘C’操作的累积要加的数为101(2),那么后五位的值∈(11010(2),1010(2)]的数在加上101(2)后就正好第五位为1。可以发现这变成了一个区间求和问题,区间求和复杂度在log数量级上的就不难想到树状数组了。
现在的问题有两个。一是怎么找n个数的后几位,二是怎么找区间的端点。找n个数的后几位我的解决方案是开15个树状数组,因为有15位,tree[i]表示的是n个数后i位所构成的树状数组。对于找区间的端点,需要用到一些位运算的处理,看代码可以很容易理解。下面是代码(望引玉>-<):
1 //T1 位统计bit 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 #define mo 65536 6 #define MAXLEN 20 7 int n,m,ans,sum; 8 int a[MAXLEN][mo];//树状数组 9 int c[MAXLEN]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072};//c[i]=1<<i 10 inline void Get_int(int &Ret) 11 { 12 char ch; 13 bool flag=false; 14 for(;ch=getchar(),ch<'0'||ch>'9';) 15 if(ch=='-') 16 flag=true; 17 for(Ret=ch-'0';ch=getchar(),ch>='0'&&ch<='9';Ret=Ret*10+ch-'0'); 18 flag&&(Ret=-Ret); 19 } 20 inline void Get_char(char &Ret) 21 { 22 char ch; 23 for(;ch=getchar(),ch!='Q'&&ch!='C';); 24 Ret=ch; 25 } 26 void Add(int k,int x) 27 { 28 while(x<=mo) 29 { 30 a[k][x]++; 31 x+=((x+1)&-(x+1));//下标从0开始 32 } 33 } 34 void Read() 35 { 36 Get_int(n); 37 Get_int(m); 38 int i,j,k,tmp; 39 for(i=1;i<=n;i++) 40 { 41 Get_int(k); 42 tmp=1; 43 for(j=0;j<=15;j++) 44 { 45 tmp=tmp<<1; 46 Add(j,k&(tmp-1)); 47 } 48 } 49 } 50 int Sum(int k,int x)//求和 51 { 52 int l=0; 53 while(x>=0) 54 { 55 l+=a[k][x]; 56 x-=((x+1)&-(x+1)); 57 } 58 return l; 59 } 60 void Work() 61 { 62 int i,j,k,t1,t2; 63 char ch; 64 for(i=1;i<=m;i++) 65 { 66 Get_char(ch); 67 Get_int(j); 68 if(ch=='C') 69 sum=(sum+j)%mo; 70 else 71 { 72 ans=0; 73 k=sum&(c[j+1]-1); 74 t1=c[j+1]-1-k; 75 if(c[j]>=k+1) 76 t2=c[j]-k-1; 77 else 78 t2=-1; 79 ans=Sum(j,t1)-Sum(j,t2);//很多数据可以这里就出答案了 但一些特殊数据不行 80 t2=c[j]+c[j+1]-k-1; 81 t1+=c[j+1]; 82 if(t2<mo) 83 { 84 if(t1>mo) 85 t1=mo; 86 ans+=Sum(j,t1)-Sum(j,t2); 87 } 88 printf("%d\n",ans); 89 } 90 } 91 } 92 int main() 93 { 94 freopen("bit.in","r",stdin); 95 freopen("bit.out","w",stdout); 96 Read(); 97 Work(); 98 return 0; 99 }
【续】
lz用的树状数组维护前缀和,显然十分麻烦,这里介绍一个新方法,DP维护前缀和实现区间询问。大致思路与上述方法差不多,这里不再赘述。下面给出代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 #define MAX_N 100010 6 #define MAX_L 16 7 const int offset[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536}; 8 int n, m, num, delta, lowbit, tot[MAX_L][1 << (MAX_L + 1)]; 9 char ch, buf[MAX_L]; 10 long long ans; 11 int main() 12 { 13 int i,j; 14 scanf("%d%d",&n,&m); 15 for(i=0;i<MAX_L;++i) 16 fill(tot[i],tot[i]+offset[i+1]+1,0); 17 for(i=0;i<n;++i) 18 { 19 scanf("%d",&num); 20 for(j=0;j!=MAX_L;++j) 21 ++tot[j][num&(offset[j+1]-1)]; 22 } 23 gets(buf); 24 for(i=0;i<MAX_L;++i) 25 for(j=1;j<=offset[i+1];++j) 26 tot[i][j]+=tot[i][j-1]; 27 delta=0; 28 for(i=0;i<m;i++) 29 { 30 ans=0; 31 scanf("%c%d\n",&ch,&num); 32 if(ch=='C') 33 delta=(delta+num)%offset[MAX_L]; 34 else 35 { 36 lowbit=delta&(offset[num+1]-1); 37 if((lowbit&offset[num])==0) 38 ans+=tot[num][offset[num+1]-lowbit-1]-(offset[num]-lowbit-1>=0?tot[num][offset[num]-lowbit-1]:0); 39 else 40 { 41 lowbit-=offset[num]; 42 ans+=tot[num][offset[num+1]-1]-tot[num][offset[num+1]-1-lowbit]+tot[num][offset[num]-lowbit-1]; 43 } 44 cout<<ans<<endl; 45 } 46 } 47 return 0; 48 }
至此,此题解决。