传送门
本题让求一个x,使得当数组a中的所有元素与它异或后形成的新数组b中的逆序对最少。
异或惯用套路是按位考虑,这里我们从高位开始考虑。当前位的所有元素无非只有0或1两种状态,异或1的话相当于01互换,也就是发生反转,考虑这样异或对数字有何影响,注意该位同为0的数字无论是否反转都不能改变它们的相对大小,该位同为1的数字也不能改变它们的相对大小。这个性质是解题的关键。
由于不太好描述,用图来举例。比如现在有5个数字
a
[
0
]
,
a
[
1
]
,
a
[
2
]
,
a
[
3
]
,
a
[
4
]
\mathbf{a[0],a[1],a[2],a[3],a[4]}
a[0],a[1],a[2],a[3],a[4],每个数字写成二进制如下图所示
我们先考虑第一行,也就是所有数字的最高位。只看这一位的话逆序对显然有3对,顺序对则有1对,那么异或1后顺序对与逆序对反转,逆序对会变成1对,顺序对则有3对。那么异或1就可以减少逆序对。
方便起见编一下组,设
{
a
[
1
]
}
\mathbf{\{a[1]\}}
{a[1]}为组1,
{
a
[
0
]
,
a
[
2
]
,
a
[
3
]
,
a
[
4
]
}
\mathbf{\{a[0],a[2],a[3],a[4]\}}
{a[0],a[2],a[3],a[4]}为组二。
我们知道逆序对的总数量是等于组一与组二逆序对数+组一内部元素逆序对总数+组二内部逆序对总数,我们在这一位无论异或1还是0只能改变组一与组二逆序对数,并且不会影响组一内部元素逆序对总数+组二内部逆序对总数,而后面更低的位也无法改变组一与组二逆序对数,故在这一位只需要选择让逆序对减少的数即可,在这个例子中需要异或1。
第一行异或1后,现在考虑第二行。
在上图中我们标注了一下组1和组2,必须要明确的是组1和组2已经互不相关了,因为无论怎么以后都是无法改变组1和组2之间的大小关系,现在只能改变组1和组2各自内部的数字间的逆序对数。
现在对于组1而言,逆序对和顺序对都为0,由于题目要求数字尽量小,我们异或0即可。
对于组2而言,逆序对有1对,顺序对有3对,显然异或0更优。
然后注意到组2内部会分成两个组(这两个组的大小关系在更低位无法被改变),组1内部只有一个数字,所以是1个组。考虑对组重新标号,那么现在就存在3个互不关联的组,可以表示为下图:
然后我们发现当前位的三个组之间的大小关系都无法再被改变,只可能改变它们内部的数字的逆序对数目,于是我们再像前两步一样check每一组,看逆序对和顺序对分别是多少,然后加起来得到当前位的逆序对和顺序对数目,这也是能够反转的数目,最后在决定当前位是异或1还是异或0。
最后我们再重新分组,以此往复,直到check到最低位我们就得到了最终答案。
本题的难点主要在于代码不太好写,大致的代码思路就是从高到低check每一位,每次check都会算出当前位的顺序对和逆序对,然后决定该位是异或1还是0,最后重新分组。我们用数组
c
o
l
[
i
]
\mathbf{col[i]}
col[i]代表元素
a
[
i
]
\mathbf{a[i]}
a[i]属于第几组,那么每次分组的时候把同一组中,即
c
o
l
\mathbf{col}
col相同的元素分成两类,一类是当前位为1的,一类是当前位为0的即可,由于不太好描述,具体步骤参考代码注释。分完的新组在下一次check中会用到。
然后组内计算逆序对就比较简单,用
c
n
t
[
i
]
\mathbf{cnt[i]}
cnt[i]统计第
i
\mathbf{i}
i组的1的数量,遍历数组元素的时候就边统计
c
n
t
\mathbf{cnt}
cnt边计算逆序对即可。
int n,a[maxn],cnt[maxn][2],tot,col[maxn],mark[maxn][2];//tot代表当前组数
ll inv=0;//逆序对总数(题目要求输出的量)
int solve(int cur){//check第cur位
int ct=0;ll ans1=0,ans2=0;//ans1是当前位逆序对数,ans2是当前位顺序对数
FOR(i,0,tot)cnt[i][0]=cnt[i][1]=0;
FOR(i,0,n){//统计逆序对和顺序对
if(a[i]&(1<<cur)){
cnt[col[i]][1]++;
ans2+=cnt[col[i]][0];
}else{
cnt[col[i]][0]++;
ans1+=cnt[col[i]][1];
}
}
FOR(i,0,tot){//mark[i][j]表示第i组中当前位为j的元素划分到的新组编号
mark[i][0]=mark[i][1]=-1;
}
tot=0;
FOR(i,0,n){
int o=(a[i]&(1<<cur))>0;//a[i]当前位为o
if(mark[col[i]][o]==-1){//如果第一次遇到第col[i]组中的当前位为o的元素
mark[col[i]][o]=tot++;//那么给一个新的编号
col[i]=mark[col[i]][o];//把编号给该元素
}else{//如果前面第col[i]组中当前位为o的元素已经出现过
col[i]=mark[col[i]][o];//那么把它的编号给当前元素即可
}
}
inv+=min(ans1,ans2);//贪心的选择最小的逆序对数
if(ans1<ans2){//逆序对小于顺序对
return 0;//当前位异或0更优
}else if(ans1>ans2){//逆序对大于顺序对
return 1<<cur;//当前位异或1更优
}else return 0;//如果两者相等,由于题目要求数字最小化,故选择0
}
int main(){
rd(&n);
rd(a,n);
tot=1;//初始有1组
int ans=0;
ROF(i,30,0){//从第30位check到第0位
ans|=solve(i);
}
wrn(inv,ans);
}