【FOI】异或问题

题意:

给出n个数ai 和m个操作 操作有两种

C x y:将ax的值改为y

Q x:求几种方案使得 b1^b2^...^bn=x(ai>=bi)

 

题解:

先推荐一个不错的题解 题目差不多 我之前就是看着题解做的

http://hi.baidu.com/billdu/item/c749952ab2ab50c2ef10f137

 

首先我们先不考虑修改 即给你n个数ai 求几种方案使得异或和为x

为了方便起见 我们将原题的条件修改为0≤bi<ai 也就是原来的a数列中每一项都加1

由于我们要求异或和 所以各个位不影响 不难想到 要转换成二进制做

 

example1

深蓝色的部分表示和ai的这一位一样

绿色部分表示ai的这一位是1 而这里是0的一位

橘黄色的部分表示不可以被随便确定的位 我们称其为“控制位”

而浅蓝色部分表示可以被随便确定的位

这种情况可能成为解的充要条件是没有橘黄色格子的列中的数异或起来和答案的这几位一样 答案显然是2^(浅蓝色格子数)

 

状态表示:

  f[i][j][k]表示前i个数 1到j-1位有"控制位"  且第j位的异或和为k的方案数(这里的第一位是指最右边的一位)

转移:

  为了方便 我们用递推的方法实现转移

  对于 f[i][j][k] 枚举l 表示要将第i+1个数的第l位的1转换为0 

  令xo[i][j]表示前i个数第j位的异或和

  当 l<j:nei=i+1,nej=j,nez=k^(第i+1个数的第j位),free=l-1

  当 l==j:nei=i+1,nej=j,nez=k,free=l-1

  当 l>j:nei=i+1,nej=l,nez=xo[i][l],free=j-1

  f[nei][nej][nek]+=f[i][j][k]<<free

 

统计答案:

  上面提到 一种状态成为解的充要条件是没有橘黄色格子的列中的数异或起来和答案的这几位一样

  我们累加f[n][i][x的第i位] 直到xo[n][i-1]不等于x的第i-1位

 

修改:

  用上面的方法每次修改都要把f数组重建 有1000次修改 每次O(n*log^2(1000)) 显然会tle

  AK想到了一种机智的方法:

  把可能变化的数放在数组的最后面 那么每次修改就最多只会影响后面的1000位

  总的时间复杂度就大概是O(1000^2*log^2(1000))

 

代码:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 typedef long long ll;
 5 using namespace std;
 6 const ll N=20001,mo=1000000007;
 7 struct info{
 8     ll t,num,s;
 9     info(const ll a=0,const ll b=0,const ll c=0):
10         t(a),num(b),s(c){}
11 }im[N];
12 struct inas{
13     ll bo,x,y;
14     inas(const ll a=0,const ll b=0,const ll c=0):
15         bo(a),x(b),y(c){}
16 }ask[N];
17 ll n,m,f[N][13][2],sum[N][13],change[N];
18 char ch;
19 inline bool cmp(info a,info b){ return a.num<b.num; }
20 void makechange(){
21     sort(im+1,im+n+1,cmp);
22     for (ll i=1;i<=n;i++) change[im[i].t]=i;
23 }
24 void push(ll x,ll y,ll z){
25     ll now=im[x+1].s,nex=x+1,ney,nez,free;
26     for (ll i=0;i<10;i++)
27     if ((now>>i)&1){
28         if (i<y) ney=y,nez=z^((now>>y)&1),free=i;
29         if (i>y) ney=i,nez=sum[x][i],free=y;
30         if (i==y) ney=i,nez=z,free=i;
31         f[nex][ney][nez]=(f[nex][ney][nez]+(f[x][y][z]<<free)%mo)%mo;
32     }
33 }
34 void makef(ll t){
35     for (ll i=t-1;i<n;i++){
36         for (ll j=0;j<10;j++){
37             f[i+1][j][0]=f[i+1][j][1]=0;
38             sum[i+1][j]=sum[i][j]^((im[i+1].s>>j)&1);
39         }
40         for (ll j=0;j<10;j++)
41         for (ll k=0;k<=1;k++)
42         if (f[i][j][k]) push(i,j,k);
43     }
44 }
45 void print(ll t){
46     ll res=0;
47     for (ll i=9;i>=0;i--){
48         res=(res+f[n][i][(t>>i)&1])%mo;
49         if (sum[n][i]!=((t>>i)&1)) break;
50     }
51     printf("%I64d\n",res);
52 }
53 int main(){
54     scanf("%I64d%I64d",&n,&m);
55     for (ll x,i=1;i<=n;i++){
56         scanf("%I64d",&x);
57         im[i]=info(i,0,x+1);
58     }
59     for (ll i=1,x,y;i<=m;i++){
60         scanf("\n%c",&ch);
61         if (ch=='Q'){
62             scanf("%I64d",&x);
63             ask[i]=inas(0,x,0);
64         }else{
65             scanf("%I64d%I64d",&x,&y);
66             ask[i]=inas(1,x+1,y+1);
67             ++im[x+1].num;
68         }
69     }
70     makechange();
71     f[0][0][0]=1;
72     makef(1);
73     for (ll i=1;i<=m;i++)
74     if (ask[i].bo){
75         ll now=change[ask[i].x];
76         im[now].s=ask[i].y;
77         makef(now);
78     }else print(ask[i].x);
79 }
View Code

 

转载于:https://www.cnblogs.com/g-word/p/3482932.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值