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

题面

XOR

题解

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

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

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

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

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

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

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

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

具体来说,对于 \(i\in I_1\)

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

那么之后我们就不用再考虑 \(a_i\) 对总和的贡献了。

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

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

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

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

那么对于当前枚举 \(x_y=0\) 的情况,此时 \(I_1\) 部分的绝对值符号就确定了,那么我们就更新 \(cnt\) 数组为 \(cnt'\) 、更新 \(cost\)\(cost'\)(记得加上 \(cnt(y,0)\times 2^y\)),并带下去递归 \(solve(I_0,y-1,cnt',cost')\)

对于当前枚举 \(x_y=1\) 的情况也是类似的,此时 \(I_0\) 部分的绝对值符号是已经确定了的。

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

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

\(y<0\),则真正的总和就是当前的 \(cost\)

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

代码如下:

#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
*/
posted @ 2022-10-30 14:02  ez_lcw  阅读(22)  评论(0编辑  收藏  举报