【XSY3913】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
*/