[题解]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

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 }

 

至此,此题解决。

 

 

 

 

 

 

posted @ 2012-12-08 21:23  zyy是一只超级大沙茶  阅读(532)  评论(1编辑  收藏  举报