FCT
\(FCT(Fate\ Cucumber\ Trick)\)
比赛中碰到的,碰到了就起个名字吧……
\(FCT\) 用于处理二元组异或和的问题。
基本操作即:对于二元组 \((x,y)\),将 \((x<<31)|y\) 插入线性基中。
这样得到的线性基可以表出原二元数组异或得到的所有组合。
ABC249G
基本问题。二元数组 \((x_i,y_i)\),选出集合 \(\mathbb{T}\),令 \(\bigoplus_{i \in \mathbb{T}} x_i \le k\) 的前提下 \(\bigoplus_{i \in \mathbb{T}} y_i\) 最大。
转化问题,对于异或和小于等于一个数,想到从高到低枚举二进制下公共前缀,钦定第一位小于后,后面的数就可以任选了。
我们枚举这个前缀,令 \(i\) 为第一个不同的位置,前 \(i\) 位确定的值为 \(x\),问题变成在 \(x\) 能被 \(x_i\) 的前 \(i\) 位通过异或表出的前提下,\(\bigoplus_{i \in \mathbb{T}} y_i\) 最大。
一个数是否能被表出显然可做,并且只和前 \(i\) 位有关,然后将前 \(i\) 位消掉后的值在 [1,30] 位上找最大异或和,即为所求。
具体实现时枚举 \(i\),每次将 \(x\) 前 \(i\) 或 \(y\) 插进线性基,复杂度 \(O(n\log^2 V)\)。
能不能再给力一点?
发现每次暴力插很劣,考虑维护的二元组线性基本身就已经可以表示出 \((x,y)\) 的所有信息,不如直接将 \((x<<31)|y\) 得到的线性基代替 \((x_i,y_i)\)。
复杂度 \(O(n\log V+ \log^3V)\)。
code
// LUOGU_RID: 196126608
// LUOGU_RID: 196076565
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e3+5;
int n;
LL a[N],b[N],k;
namespace CK
{
LL ck[2][70];
inline void clear() {memset(ck,0,sizeof(ck));}
inline LL ins(LL x,int p)
{
for(int i=61;i>=0;i--) if((x>>i)&1)
{
if(!ck[p][i]) {ck[p][i]=x; return x;}
x^=ck[p][i];
}
return x;
}
inline LL que(LL x,int p)
{
for(int i=61;i>=31;i--) if((x>>i)&1)
{
if(!ck[p][i]) return -1;
x^=ck[p][i];
}
if(x>=(1ll<<31)) return -1;
for(int i=30;i>=0;i--) if((x^ck[p][i])>x) x^=ck[p][i];
return x;
}
} using namespace CK;
int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout);
scanf("%d%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]),ins((a[i]<<31)|b[i],1);
bool fl=0;
for(int i=1;i<=n;i++) fl|=(ins(a[i],0)<=k);
if(!fl) return printf("-1\n"),0;
int cnt=0;
for(int i=61;i>=0;i--) if(ck[1][i]) a[++cnt]=(ck[1][i]>>31),b[cnt]=(ck[1][i]&INT_MAX);
n=cnt;
LL ans=-1;
for(int i=30;i>=-1;i--)
{
if(i!=-1&&!((k>>i)&1)) continue;
clear();
LL t=k; if(i!=-1) t=((t>>i)^1)<<i; t<<=31;
if(i!=-1) {for(int j=1;j<=n;j++) ins((((a[j]>>i)<<i)<<31)|b[j],0);}
else {for(int j=1;j<=n;j++) ins((a[j]<<31)|b[j],0);}
ans=max(ans,que(t,0));
}
printf("%lld\n",ans);
return 0;
}
命运黄之瓜
第一次见到这个 trick,有玩 gal 的来解释一下?
你已经会上面那道题了,考虑转化。
首先大最小值想到二分,即二分一个 \(k\) 使 \(a\) 的异或和不小于 \(k\) 的前提下 \(b\) 的异或和最大。
然后就是上面那道题了,对于线性基可以预处理。复杂度 \(O(n\log V + \log^3V)\)。
code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5+5;
int n,T;
LL a[N],b[N];
namespace CK
{
LL ck[40][70];
inline void clear() {memset(ck,0,sizeof(ck));}
inline void ins(LL x,int p)
{
for(int i=61;i>=0;i--) if((x>>i)&1)
{
if(!ck[p][i]) {ck[p][i]=x; return;}
x^=ck[p][i];
}
}
inline LL que(LL x,int p)
{
for(int i=61;i>=31;i--) if((x>>i)&1)
{
if(!ck[p][i]) return -1;
x^=ck[p][i];
}
if(x>=(1ll<<31)) return -1;
for(int i=30;i>=0;i--) if((x^ck[p][i])>x) x^=ck[p][i];
return x;
}
inline LL sol(LL k)
{
LL res=-1;
for(int i=30;i>=-1;i--)
{
if(i!=-1&&((k>>i)&1)) continue;
LL t=k; if(i!=-1) t=((t>>i)^1)<<i; t<<=31;
if(i!=-1) res=max(res,que(t,i)); else res=max(res,que(t,0));
}
return res;
}
} using namespace CK;
inline bool check(LL mid)
{
return sol(mid)>=mid;
}
#define gc getchar_unlocked
inline LL read()
{
LL res=0; char x=gc();
while(x<'0'||x>'9') x=gc();
while(x>='0'&&x<='9') res=(res<<1)+(res<<3)+(x^48),x=gc();
return res;
}
int main()
{
// freopen("in.in","r",stdin);
// freopen("out.out","w",stdout);
T=read();
while(T--)
{
n=read(); clear();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) b[i]=read();
for(int i=1;i<=n;i++) ins((a[i]<<31)|b[i],31);
int cnt=0;
for(int i=61;i>=0;i--) if(ck[31][i]) a[++cnt]=(ck[31][i]>>31),b[cnt]=(ck[31][i]&INT_MAX);
n=cnt;
for(int i=30;i>=0;i--)
for(int j=1;j<=n;j++)
ins((((a[j]>>i))<<i)<<31|b[j],i);
LL l=0,r=INT_MAX,res=0;
while(l<=r)
{
LL mid=l+r>>1;
if(check(mid)) l=mid+1,res=mid;
else r=mid-1;
}
printf("%lld\n",res);
}
// cerr<<(1000.0*clock()/CLOCKS_PER_SEC);
return 0;
}