题解:CF2247D2 XOR Sorting (Hard Version)

· · 题解

题意

交换 a_ia_j 代价为 i \oplus j,求将数组排好序中所有代价最大值的最小,随后给出 q 次不可逆单点修改,并求出修改后序列的代价。

题解

::::info[回顾] 前情提要。

简单提一嘴 Easy Version,我们得到最终交换两个数的代价最小值一定是 2^p,其中 p 是这两个数的最高不同位,然后我们又发现形如 [x2^{p+1},(x+1)2^{p+1}) 的区间任意交换这里面两个数代价不超过 2^p,于是我们可以枚举最终贡献,比较前一个区间的最大和后一个区间最小值即可。 :::: 有了 Easy Version 的做法,Hard Version 如喝水。

下面的区间指的是形如 [x2^{p+1},(x+1)2^{p+1}) 的区间。

我们发现一次修改只会影响 \log n 个区间,于是我们可以先预处理每个长度区间不符合要求相邻区间对的个数,然后线段树区间修改每一个区间,修改完判断一下就行,复杂度 O(n\log n +q \log ^2 n)

我们令 $mx_{i,j},mn_{i,j}$ 表示长度为 $2^j$ 的第 $i $ 个区间的最大值和最小值。 我们发现这个区间成某种神秘的递推关系,区间长度为 $2^p$ 所以我们可以只修改最小的长度为 $1$ 贡献为 $0 $ 的区间,然后对于长度为 $2^p$ 的第 $k$ 个区间,他新最值为 $\max\{mx_{p-1,k\times2},mx_{p-1,k\times2+1}\}$ 和$\min\{mn_{p-1,k\times2},mn_{p-1,k\times2+1}\}$。 于是我们得到了单次 $O(\log n)$ 的 $O((n+q)\log n)$ 写法。 或许你可以发现这货类似线段树,但是还是和线段树有区别的,最后就是会有一些块不满 $2^p$ 记得边界处理。 ::::info[代码] ```cpp //千万记得判边界 #include<bits/stdc++.h> using namespace std; const int N=1e6+10; int n,a[N],q; int mx[25][N],mn[25][N],cnt[25]; inline int ls(int p){return p<<1;} inline int rs(int p){return p<<1|1;} void update(int x,int d){ if(x>0&&mx[0][x-1]>mn[0][x])cnt[0]--;//处理旧答案 if(x<n-1&&mx[0][x]>mn[0][x+1])cnt[0]--; a[x]=d; mx[0][x]=mn[0][x]=d; if(x>0&&mx[0][x-1]>mn[0][x])cnt[0]++; if(x<n-1&&mx[0][x]>mn[0][x+1])cnt[0]++;//处理新答案 for(int i=1;i<=20;i++){ int len=1<<i;//这块大小 int k=x/len; int tot=(n+len-1)/len;//块数,千万要上取整 if(k>=tot)break; if(k>0&&mx[i][k-1]>mn[i][k])cnt[i]--; if(k<tot-1&&mx[i][k]>mn[i][k+1])cnt[i]--; int mid=k*len+len/2;//边界处理 if(mid>=n)mx[i][k]=mx[i-1][ls(k)],mn[i][k]=mn[i-1][ls(k)]; else mx[i][k]=max(mx[i-1][ls(k)],mx[i-1][rs(k)]),mn[i][k]=min(mn[i-1][ls(k)],mn[i-1][rs(k)]); if(k>0&&mx[i][k-1]>mn[i][k])cnt[i]++; if(k<tot-1&&mx[i][k]>mn[i][k+1])cnt[i]++; } } int query(){ for(int i=0;i<=20;i++){ if(cnt[i]==0)return (i==0?0:(1<<(i-1))); } return 1<<20; } int main(){ cin.tie(0);ios::sync_with_stdio(0); int T; cin>>T; while(T--){ cin>>n>>q; for(int i=0;i<=20;i++)cnt[i]=0; for(int i=0;i<n;i++){ cin>>a[i]; mx[0][i]=mn[0][i]=a[i]; } for(int i=1;i<=20;i++){ int len=1<<i; int tot=(n+len-1)/len; if(tot==0)break; for(int j=0;j<tot;j++){ int mid=j*len+len/2;//边界处理,就是如果j是后面不完整的直接继承 if(mid>=n)mx[i][j]=mx[i-1][ls(j)],mn[i][j]=mn[i-1][ls(j)]; else mx[i][j]=max(mx[i-1][ls(j)],mx[i-1][rs(j)]),mn[i][j]=min(mn[i-1][ls(j)],mn[i-1][rs(j)]); } } for(int i=0;i<=20;i++){ int len=1<<i; int tot=(n+len-1)/len; for(int j=0;j<tot-1;j++){ if(mx[i][j]>mn[i][j+1])cnt[i]++; } } cout<<query()<<'\n'; while(q--){ int x,d; cin>>x>>d; update(x,d); cout<<query()<<'\n'; } } return 0; } ``` ::::