每日一題-24-06-17 (P10218)(加倍!)

Kent530發表於2024-06-17

看到異或直接想到線性基和trie

很明顯是trie

從高到低一位位考慮,如果兩個兒子都有,想使這一位為1,必須有一個變成加法

然後就便利一下trie,記錄一下剩餘的體力和最小的加法的數就好了

#include<bits/stdc++.h>
using namespace std;
#define ll __int128
#define ls tr[u][0]
#define rs tr[u][1]
ll read(){
	char ch=getchar();ll x=0;
	while(ch<'0' || ch>'9')ch=getchar();
	while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch-'0'),ch=getchar();
	return x;
}
void write(ll x){
	if(x>=10)write(x/10);
	putchar(x%10+'0');
}
int T,C,n,m,k,b[100005],tr[12000005][2],cnt;
ll res,a[100005],sumb[12000005],minb[12000005];
void init(){
	res=0;
	for(int i=1;i<=cnt;i++)tr[i][0]=tr[i][1]=sumb[i]=0,minb[i]=(ll)1<<120;
	cnt=1;
}
void insert(ll x,int y){
	int u=1;
	sumb[u]+=y;minb[u]=min(minb[u],x);
	for(int i=k-1;i>=0;i--){
		int p=(x>>i)&1;
		if(!tr[u][p])tr[u][p]=++cnt;
		u=tr[u][p];sumb[u]+=y;minb[u]=min(minb[u],x);
	}
}
void work(int u,int id,int la,ll s,ll x,ll miny){
	if(id==-1){
		res=max(res,s);
		return ;
	}
	ll Mk=(ll)1<<id,mk=Mk-1;
	if(!u){
		res=max(res,miny+(x|Mk|mk));
		return ;
	}
	bool flag=1;
	if(sumb[ls]<=la && (x|mk)+min(miny,minb[ls])>=(s|Mk))
		work(rs,id-1,la-sumb[ls],s|Mk,x,min(miny,minb[ls])),flag=0;
	if(sumb[rs]<=la && (x|Mk|mk)+min(miny,minb[rs])>=(s|Mk))
		work(ls,id-1,la-sumb[rs],s|Mk,x|Mk,min(miny,minb[rs])),flag=0;
	if(flag){
		work(ls,id-1,la,s,x,miny);
		work(rs,id-1,la,s,x|Mk,miny);
	}
	return ;
}
int main(){
	scanf("%d%d",&C,&T);
	memset(minb,0x7f,sizeof(minb));
	while(T--){
		scanf("%d%d%d",&n,&m,&k);
		init();
		for(int i=1;i<=n;i++)a[i]=read();
		for(int i=1;i<=n;i++)scanf("%d",&b[i]);
		for(int i=1;i<=n;i++)insert(a[i],b[i]);
//		for(int i=1;i<=cnt;i++)printf("(%d %d)\n",tr[i][0],tr[i][1]);
//		for(int i=1;i<=cnt;i++)write(sumb[i]),putchar(' ');puts("");
//		for(int i=1;i<=cnt;i++)write(minb[i]),putchar(' ');puts("");
		if(sumb[1]<=m){
			write(minb[1]+((ll)1<<k)-1);putchar('\n');
			continue;
		}
		work(1,k-1,m,0,0,(ll)1<<120);
		write(res);putchar('\n');
	}
	return 0;
}

本來想到了,但是以為那個work的列舉是 \(O(2^k)\) ,卡了半天(降智了)

相關文章