Bitwise Xor-题解
luogu通道------>别点进去,没有通道
题意
给你一个\(n\)长度的序列,叫你求有多少个子序列满足:
这子序列中任意两个数取反的值≥X
输入
\(n\;\;x\)
\(a_1\;a_2\;a_3\dots a_n\)
数据范围
\[X\leq 2^{60}-1
\]
\[a_i\leq 2^{60}-1,i\in[1,n]
\]
\[n\leq 3\times 10^5
\]
分析
刚拿到这道题时,我心中只有暴力(可是这是CF赛制)
然后就是听老师讲“若干个数两两异或的最小值必在排序后相邻两个取到”。可是我并没法理解(当然,我当时以为是“或”而不是“异或”)。
所以当我知道是异或的时候,我自己脑袋里面已经有了一些思路。
我们设\(x\leq y\leq z\)
\(⊕\)是取反的意思
然后我们可以知道:
- \((x⊕y)⊕(y⊕z)==z⊕x\)
这相当于是将y给抵消了吧。
口头证明:
(只可意会不可言传)
好吧,这个坑我之后再补,可是应该能够想象出来,但是也可以用代码做下实验
int x1,x2,x3;
cin>>x1>>x2>>x3;
int judge1=x1^x2,judge2=x2^x3;
if(x1^x3==judge1^judge2) cout<<"TRUE"<<endl;
else cout<<"False"<<endl;
- \(min(x⊕y,y⊕z)\leq x⊕z\)
\[\dots\dots
\]
我也不会证明,可是代码验证是对的
根据上面的两个式子,我们就可以将这个当成DP来做了
\(dp[i]\)代表的是选择\(a_i\)为子序列的末尾,有多少个合法的子序列。
转移方程就是:
\[f[i]=1+\sum_{j=1,f[i]⊕f[j]>=x}^{i-1}f[j]
\]
为什么要\(+1\)呢,因为单独一个数也可以构成合法的子序列(我也不知道为什么)
所以我们就得到了方程。
那怎么统计答案呢?
我们思考,\(f[i]\)代表的意思是什么?
\(f[i]\)代表的是i必选,有多少个合法序列。-->这个是根据状态转移得出的。
所以最终答案就是\(\sum_{i=1}^{n}f[i]\)
而我们得出的方程式的时间复杂度为\(O(n^2)\)
怎么优化?
根据题意可知道,我们可以使用\(trie\)优化,具体细节看代码就懂
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ri register int
#define Starseven main
#define ll long long
using namespace std;
const int N=3e5+20;
const ll mod=998244353;
ll a[N],x,f[N],sum[N*60];
int n,ch[N*60][2],cnt=1;//因为起点是从1开始的,所以cnt应该从2开始,初始化为一
void Insert(ll add,ll s){
int u=1;
for(ri i=60;i>=0;i--){
int c=((s>>i)&1);
if(!ch[u][c]) ch[u][c]=++cnt;
u=ch[u][c];sum[u]=(sum[u]+add)%mod;
}
return ;
}
ll Query(ll s){
ll re=0;
int u=1;
for(ri i=60;i>=0;i--){
if((x>>i)&1) u=ch[u][((s>>i)&1)==0];//这里保证了经过的点在XOR过程中始终=x
else{
re=(re+sum[ch[u][((s>>i)&1)==0]])%mod;
u=ch[u][((s>>i)&1)!=0];//理解
}
}
return (re+sum[u])%mod;
}
int Starseven(void){
cin>>n>>x;
for(ri i=1;i<=n;i++) cin>>a[i];
sort(a+1,a+1+n);
for(ri i=1;i<=n;i++){
f[i]=(Query(a[i])+1)%mod;
Insert(f[i],a[i]);
}
ll ans=0;
for(ri i=1;i<=n;i++) ans=(ans+f[i])%mod;
cout<<ans<<endl;
return 0;
}