CF1188D Make Equal
题意
给出\(n\)个数\(a_i\),每次操作可以给一个数加上\(2^k\)。问最少需要多少次操作来使得该数组的每个元素都相等。
\(n\le 10^5,a_i \le 10^{17}\)
思路
即求数组\(b\),使得\(a_i+b_i\)都相同且\(\sum \text{bitcount}(b_i)\)最小
排序差分,最大的数改变\(b_n\),则\(b_i=b_n+(a_n-a_i)\),求\(b_n\)使得,\(\sum \text{bitcount}(b_n+(a_n-a_i))\)最小
然后就变成了一道双倍经验题CF778E
很容易想到记录后一位的进位情况,然后从低位到高位转移,\(dp[i][j]\)表示当前考虑到第\(i\)位,\(j\)中为\(1\)的数后一位有进位,枚举\(b_n\)当前位上是\(0\)还是\(1\),但是这样子状态数是\(2^n\),思考有没有优化方法。
越大的数越容易进位,在只考虑后面位的时候,也就是按\(0 \text{~} i\)位从大到小排序(只有两个数的基数排序),前面的越容易进位。也就是一个前缀,所以只要将\(O(2^n)\)枚举变成\(O(n)\)枚举前缀长度,从小到大依次改变代价和进位数量就可以了。
#include <bits/stdc++.h>
typedef long long ll;
const int N=100005,INF=N*1000;
int rank[N],dp[70][N],tmp[N],n;
ll a[N],p[70];
void Sort(int x){
int k=0;
for (int i=1;i<=n;i++)
if (a[rank[i]]&p[x]) tmp[++k]=rank[i];
for (int i=1;i<=n;i++)
if ((a[rank[i]]&p[x])==0) tmp[++k]=rank[i];
for (int i=1;i<=n;i++) rank[i]=tmp[i];
}
void solve(int w,int x){
int cnt=0,sum=0;
for (int i=1;i<=n;i++){
int t=(a[i]&p[w])>0;
if (t+x==2) cnt++;
if (t+x==1) sum++;
}
if (dp[w][0]<INF) dp[w+1][cnt]=std::min(dp[w+1][cnt],dp[w][0]+sum);
for (int i=1;i<=n;i++){
int t=(a[rank[i]]&p[w])>0;
if ((t+x+1)%2) sum++;
if (t+x+1==2) cnt++,sum--;
if (dp[w][i]<INF) dp[w+1][cnt]=std::min(dp[w+1][cnt],dp[w][i]+sum);
}
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
std::sort(a+1,a+n+1);
ll x=a[n];int w=0;
while (x) x/=2,w++;
p[0]=1;
for (int i=1;i<=w;i++) p[i]=p[i-1]<<1;
memset(dp,0x3f,sizeof(dp));
dp[0][0]=0;
for (int i=1;i<=n;i++) rank[i]=i;
for (int i=1;i<=w+1;i++){
for (int j=0;j<=1;j++)
solve(i-1,j);
Sort(i-1);
}
int ans=INF;
printf("%d\n",dp[w+1][0]);
}
后记
继咕咕咕很久以及起始考爆炸之后的浑浑噩噩
* 生而自由 爱而无畏 *

浙公网安备 33010602011771号