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;
}
posted @ 2024-12-26 19:49  ppllxx_9G  阅读(8)  评论(0编辑  收藏  举报