CF1188D Make Equal
链接
题解
首先知道如果最后每个数都变成了 \(x\),那么答案就是 \(\min\big(\sum \operatorname{popcount}(x-a_i) \big)\)。因为减感觉比较麻烦,所以考虑最大的 \(a\) 加上的值为 \(x\),答案就是 \(\min\big(\sum \operatorname{popcount}(x+a_n-a_i) \big)\),于是令 \(a_i=a_n-a_i\) 就转化成加了。
然后这里因为 \(a\le 10^{17}\) 故 \(a\) 最大只会加到 \(2^{57}-1\),\(x\) 就有个范围了。
然后我们考虑加的时候一个数的 \(\operatorname{popcount}\) 会怎么变。
按位考虑,每一位上需要考虑的有三个 \(1\) 的来源,分别是 \(a_i\) 当前位,\(x\) 和 \(a_i\) 上一位的进位。
进位很不好处理,如果要设状态就会设成 \(2^n\)。但是我们注意到因为所有数加的 \(x\) 都一样,所以在 \(i\) 位之下,越大越容易进位到 \(i\)。所以我们只需要排个序就可以把进位的数变成一段前缀,状态数变成 \(n\)。
那么就可以设 \(dp[i][j]\) 表示考虑了前 \(i\) 位,第 \(i-1\) 位往第 \(i\) 位进了 \(j\) 个 \(1\) 时最小的答案。
那么考虑转移,首先明确这一位上留下来 \(1\) 的个数计入答案,进位出去的 \(1\) 在转移状态里。
下面用前面和后面代指前 \(j\) 个有进位的位置和后 \(n-j\) 个没有进位上来的位置,并记 \(cnt\) 为所有 \(a\) 第 \(i\) 位 \(1\) 的总个数,\(tot\) 为前 \(j\) 个 \(a\) 第 \(i\) 位 \(1\) 的总个数。
如果 \(x\) 第 \(i\) 位是 \(0\) 的话,进位的位置是前面的 \(1\),留下来 \(1\) 的位置是前面的 \(0\) 和后面的 \(1\)。所以 \(dp[i+1][tot]=\min(dp[i+1][tot],dp[i][j]+(i-tot)+(cnt-tot))\);
如果 \(x\) 第 \(i\) 位是 \(1\) 的话,进位的位置是前面所有以及后面的 \(1\),留下来 \(1\) 的位置是前面的 \(1\) 和后面的 \(0\)。所以 \(dp[i+1][j+(cnt-tot)]=\min(dp[i+1][j+(cnt-tot)],dp[i][j]+tot+(n-j-cnt+tot))\)。
这样就做完了,时间复杂度为 \(O(n\log n \cdot \log a)\),瓶颈在排序,可以改成基排,时间复杂度就是 \(O(n\log a)\)。
还不懂建议看代码。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define cs const
#define in read()
inline int read(){
int p=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c)){p=p*10+c-48;c=getchar();}
return p*f;
}
cs int N=1e5+5;
int n,a[N];
int dp[58][N];
int id[N],nm;
inline bool cmp(int x,int y){return a[x]-((a[x]>>nm)<<nm)>a[y]-((a[y]>>nm)<<nm);}
inline void ckmn(int &x,int y){if(y<x)x=y;}
signed main(){
n=in;for(int i=1;i<=n;i++)a[i]=in;
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)a[i]=a[n]-a[i],id[i]=i;
for(int i=0;i<=57;i++)
for(int j=0;j<=n;j++)
dp[i][j]=1000000000;
dp[0][0]=0;
for(int T=0,cnt,tot;T<57;T++){
sort(id+1,id+1+n,cmp),nm++,cnt=tot=0;
for(int i=1;i<=n;i++)cnt+=(a[i]>>T)&1;//total 1
for(int i=0;i<=n;i++){
tot+=(a[id[i]]>>T)&1;//totol 1 from[1,i]
ckmn(dp[T+1][tot]/*x[T]=0*/,dp[T][i]+(cnt-tot)/*R 1*/+(i-tot)/*L 1*/);
ckmn(dp[T+1][i+(cnt-tot)]/*x[T]=1*/,dp[T][i]+tot/*L 1*/+(n-i-cnt+tot)/*R 0*/);
}
}
cout<<dp[57][0];
return 0;
}
只能被神仙题爆切/kk