【XSY3913】XOR(递归,分治,剪枝)

这篇博客详细介绍了如何利用二进制表示和绝对值的关系解决计算问题。通过枚举数字的二进制位,确定绝对值符号,并分析不同情况下对总和的贡献,最终给出了一种O(nlog^2V)复杂度的解决方案。文章以一道编程题目为例,解释了算法的实现过程和思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题面

XOR

题解

以下对于一个数 x x x,用 x i x_i xi 表示 x x x 在二进制下的第 i i i 位。如果这个数本身就带下标,如 a k a_k ak,那么用 a k , i a_{k,i} ak,i 表示 a k a_k ak 在二进制下的第 i i i 位。

发现带上绝对值很难搞,考虑如何确定绝对值的符号:

对于 ∣ a − b ∣ |a-b| ab 来说,我们找到 a a a b b b 在二进制下最高的不同位 y y y,那么如果 a y = 1 a_y=1 ay=1(那么 b y = 0 b_y=0 by=0),有 ∣ a − b ∣ = a − b |a-b|=a-b ab=ab;如果 a y = 0 a_y=0 ay=0(那么 b y = 1 b_y=1 by=1),有 ∣ a − b ∣ = b − a |a-b|=b-a ab=ba

考虑从高到低枚举 x x x 二进制下的每一位,然后统计每一种情况的总和,最后答案就是总和的最小值。

枚举 x x x 过程中我们可以通过刚刚说的方法确定一些 ∣ a i − ( b i ⊕ x ) ∣ |a_i-(b_i\oplus x)| ai(bix) 的绝对值的符号(下面会讲),显然初始时(枚举前)所有绝对值的符号都是不确定的。

那么设当前枚举到第 y y y 位,设当前 ∣ a i − ( b i ⊕ x ) ∣ |a_i-(b_i\oplus x)| ai(bix) 绝对值符号还未确定的 i i i 的集合为 I I I。那么显然对于任意的 i ∈ I i\in I iI j > y j>y j>y,都有 a i , j = ( b i ⊕ x ) j a_{i,j}=(b_i\oplus x)_j ai,j=(bix)j,不然绝对值符号就是可以确定的。那么对于 i ∈ I i\in I iI ∣ a i − ( b i ⊕ x ) ∣ |a_i-(b_i\oplus x)| ai(bix) 中比 y y y 更高的位对当前的总和都是没有贡献的。

不妨设当前位为第 y y y 位,按照 a i a_i ai b i b_i bi 在当前位的情况可以把 I I I 分成两个集合:若 a i , y = b i , y a_{i,y}=b_{i,y} ai,y=bi,y 则把 i i i 分到 I 0 I_0 I0 集合;若 a i , y ≠ b i , y a_{i,y}\neq b_{i,y} ai,y=bi,y 则把 i i i 分到 I 1 I_1 I1 集合。

假设枚举 x x x 当前位为 0 0 0,那么 I 1 I_1 I1 部分的绝对值的符号是已经确定了的。

具体来说,对于 i ∈ I 1 i\in I_1 iI1

  • a i , y = 1 a_{i,y}=1 ai,y=1(那么 b i , y = 0 b_{i,y}=0 bi,y=0),则 ( b i ⊕ x ) y = 0 (b_i\oplus x)_y=0 (bix)y=0,则 ∣ a i − ( b i ⊕ x ) ∣ = a i − ( b i ⊕ x ) |a_i-(b_i\oplus x)|=a_i-(b_i\oplus x) ai(bix)=ai(bix)。那么此时 − ( b i ⊕ x ) -(b_i\oplus x) (bix) 的第 y y y 位和 a i a_i ai 对于当前的总和的贡献已经可以确定了,为 2 y + ( a i and ⁡   ( 2 y − 1 ) ) 2^y+(a_i \operatorname{and}\,(2^y-1)) 2y+(aiand(2y1))
  • a i , y = 0 a_{i,y}=0 ai,y=0(那么 b i , y = 1 b_{i,y}=1 bi,y=1),则 ( b i ⊕ x ) y = 0 (b_i\oplus x)_y=0 (bix)y=0,则 ∣ a i − ( b i ⊕ x ) ∣ = ( b i ⊕ x ) − a i |a_i-(b_i\oplus x)|=(b_i\oplus x)-a_i ai(bix)=(bix)ai。那么此时 ( b i ⊕ x ) (b_i\oplus x) (bix) 的第 y y y 位和 − a i -a_i ai 对于当前的总和的贡献已经可以确定了,为 2 y − ( a i and ⁡   ( 2 y − 1 ) ) 2^y-(a_i \operatorname{and}\,(2^y-1)) 2y(aiand(2y1))

那么之后我们就不用再考虑 a i a_i ai 对总和的贡献了。

而对于这些确定了绝对值符号的 b i b_i bi 0 ∼ y − 1 0\sim y-1 0y1 位对总和的贡献,我们还需根据接下来枚举的 x x x 另行讨论。具体来说,我们记录一个数组 c n t ( j , v ) cnt(j,v) cnt(j,v) 表示:如果 x x x j j j 位选了 v v v,那么这些已经确定了绝对值符号的 b i b_i bi 会对总和贡献 c n t ( j , v ) × 2 j cnt(j,v)\times 2^j cnt(j,v)×2j

那么我们重新回到刚刚的那个讨论:(此时仍是假设枚举 x x x 当前位为 0 0 0,然后对于 i ∈ I 1 i\in I_1 iI1

  • a i , y = 1 a_{i,y}=1 ai,y=1,则刚刚我们确定了 ∣ a i − ( b i ⊕ x ) ∣ = a i − ( b i ⊕ x ) |a_i-(b_i\oplus x)|=a_i-(b_i\oplus x) ai(bix)=ai(bix)。那么此时对于所有的 0 ≤ j < y 0\leq j<y 0j<y,如果 x j x_j xj 取了 ( b i , j ⊕ 1 ) (b_{i,j}\oplus 1) (bi,j1),那么 − ( b i ⊕ x ) -(b_i\oplus x) (bix) 的第 j j j 位就会对总和贡献 − 2 j -2^j 2j,所以应该要让 c n t ( j , b i , j ⊕ 1 ) ← c n t ( j , b i , j ⊕ 1 ) − 1 cnt(j,b_{i,j}\oplus 1)\gets cnt(j,b_{i,j}\oplus 1)-1 cnt(j,bi,j1)cnt(j,bi,j1)1
  • a i , y = 0 a_{i,y}=0 ai,y=0,则刚刚我们确定了 ∣ a i − ( b i ⊕ x ) ∣ = ( b i ⊕ x ) − a i |a_i-(b_i\oplus x)|=(b_i\oplus x)-a_i ai(bix)=(bix)ai。那么此时对于所有的 0 ≤ j < y 0\leq j<y 0j<y,如果 x j x_j xj 取了 ( b i , j ⊕ 1 ) (b_{i,j}\oplus 1) (bi,j1),那么 ( b i ⊕ x ) (b_i\oplus x) (bix) 的第 j j j 位就会对总和贡献 2 j 2^j 2j,所以应该要让 c n t ( j , b i , j ⊕ 1 ) ← c n t ( j , b i , j ⊕ 1 ) + 1 cnt(j,b_{i,j}\oplus 1)\gets cnt(j,b_{i,j}\oplus 1)+1 cnt(j,bi,j1)cnt(j,bi,j1)+1

那么我们可以进行这么一个递归函数: s o l v e ( I , y , c n t , c o s t ) solve(I,y,cnt,cost) solve(I,y,cnt,cost) 表示当前枚举到第 y y y 位、当前 ∣ a i − ( b i ⊕ x ) ∣ |a_i-(b_i\oplus x)| ai(bix) 绝对值符号还未确定的 i i i 的集合为 I I I、在第 y y y 位以前记录的 c n t cnt cnt 数组、和在第 y y y 位以前记录的总和 c o s t cost cost

那么对于当前枚举 x y = 0 x_y=0 xy=0 的情况,此时 I 1 I_1 I1 部分的绝对值符号就确定了,那么我们就更新 c n t cnt cnt 数组为 c n t ′ cnt' cnt 、更新 c o s t cost cost c o s t ′ cost' cost(记得加上 c n t ( y , 0 ) × 2 y cnt(y,0)\times 2^y cnt(y,0)×2y),并带下去递归 s o l v e ( I 0 , y − 1 , c n t ′ , c o s t ′ ) solve(I_0,y-1,cnt',cost') solve(I0,y1,cnt,cost)

对于当前枚举 x y = 1 x_y=1 xy=1 的情况也是类似的,此时 I 0 I_0 I0 部分的绝对值符号是已经确定了的。

边界条件是当前 I = ∅ I=\empty I= y < 0 y<0 y<0

I = ∅ I=\empty I=,说明所有 i i i 的绝对值符号已经确定了,那么我们根据当前的 c n t cnt cnt 数组来计算真正的总和: s u m = c o s t + ∑ j = 0 y min ⁡ ( c n t ( j , 0 ) , c n t ( j , 1 ) ) × 2 j sum=cost+\sum\limits_{j=0}^{y}\min(cnt(j,0),cnt(j,1))\times 2^j sum=cost+j=0ymin(cnt(j,0),cnt(j,1))×2j。而对于满足条件的 x x x 的个数,我们只需找出计算过程中有多少个 j j j 满足 c n t ( j , 0 ) = c n t ( j , 1 ) cnt(j,0)=cnt(j,1) cnt(j,0)=cnt(j,1)(这说明这一位既可以取 0 0 0 和取 1 1 1 的贡献是一样的),假设有 w w w 个,那么满足条件的 x x x 的个数为 2 w 2^w 2w

y < 0 y<0 y<0,则真正的总和就是当前的 c o s t cost cost

V V V 表示值域。注意到递归树上每一层中所有点的 I I I 集合大小的总和都不变,为 n n n,所以处理每一层的总时间都是 O ( n log ⁡ V ) O(n\log V) O(nlogV) 的,故总时间复杂度为 O ( n log ⁡ 2 V ) O(n\log^2 V) O(nlog2V)

代码如下:

#include<bits/stdc++.h>

#define N 40010
#define int long long
#define ll long long
#define LNF 0x7fffffffffffffff

using namespace std;

inline ll read()
{
	ll x=0;
	int f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct data
{
	ll v,num;
	data(){};
	data(ll a,ll b){v=a,num=b;}
	void update(data b)
	{
		if(b.v<v) v=b.v,num=b.num;
		else if(b.v==v) num+=b.num;
	}
};

struct pi
{
	ll a,b;
}p[N],I0[N],I1[N];

int n;
int cnt[50][2];
ll pow2[50];

inline bool get(ll x,int y)
{
	return (x>>y)&1;
}

data solve(int y,int l,int r,ll precost)
{
	if(l>r)
	{
		int w=0;
		ll tmp=precost;
		for(int i=0;i<=y;i++)
		{
			tmp+=min(cnt[i][0],cnt[i][1])*pow2[i];
			if(cnt[i][0]==cnt[i][1]) w++;
		}
		return data(tmp,pow2[w]);
	}
	if(y<0) return data(precost,1);
	int cnt0=0,cnt1=0;
	for(int i=l;i<=r;i++)
	{
		if(get(p[i].a,y)==get(p[i].b,y)) I0[++cnt0]=p[i];
		else I1[++cnt1]=p[i];
	}
	int mid=l+cnt0-1;
	for(int i=1;i<=cnt0;i++) p[l+i-1]=I0[i];
	for(int i=1;i<=cnt1;i++) p[mid+i]=I1[i];
	data ans=data(LNF,114514);
	int now[50][2];
	if(1)//x=1
	{
		memcpy(now,cnt,sizeof(now));
		ll tmp=precost;
		if(l<=mid)
		{
			//I0
			for(int i=l;i<=mid;i++)
			{
				if(get(p[i].a,y))
				{
					tmp+=pow2[y]+(p[i].a&(pow2[y]-1));
					for(int j=0;j<y;j++)
						cnt[j][get(p[i].b,j)^1]--;
				}
				else
				{
					tmp+=pow2[y]-(p[i].a&(pow2[y]-1));
					for(int j=0;j<y;j++)
						cnt[j][get(p[i].b,j)^1]++;
				}
			}
		}
		tmp+=cnt[y][1]*pow2[y];
		ans.update(solve(y-1,mid+1,r,tmp));
		memcpy(cnt,now,sizeof(cnt));
	}
	if(1)//x=0
	{
		memcpy(now,cnt,sizeof(now));
		ll tmp=precost;
		if(mid+1<=r)
		{
			//I1
			for(int i=mid+1;i<=r;i++)
			{
				if(get(p[i].a,y))
				{
					tmp+=pow2[y]+(p[i].a&(pow2[y]-1));
					for(int j=0;j<y;j++)
						cnt[j][get(p[i].b,j)^1]--;
				}
				else
				{
					tmp+=pow2[y]-(p[i].a&(pow2[y]-1));
					for(int j=0;j<y;j++)
						cnt[j][get(p[i].b,j)^1]++;
				}
			}
		}
		tmp+=cnt[y][0]*pow2[y];
		ans.update(solve(y-1,l,mid,tmp));
		memcpy(cnt,now,sizeof(cnt));
	}
	return ans;
}

signed main()
{
	n=read();
	pow2[0]=1ll;
	for(int i=1;i<=46;i++) pow2[i]=pow2[i-1]<<1ll;
	for(int i=1;i<=n;i++) p[i].a=read();
	for(int i=1;i<=n;i++) p[i].b=read();
	data ans=solve(46,1,n,0);;
	printf("%lld %lld\n",ans.v,ans.num);
	return 0;
}
/*
5
3 1 5 2 4
4 2 1 5 4
*/
/*
3
3 3 2
2 0 2
*/
/*
2
5 8
6 5
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值