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]);
}

后记

继咕咕咕很久以及起始考爆炸之后的浑浑噩噩

posted @ 2020-04-16 10:30  flyfeather  阅读(284)  评论(0)    收藏  举报