CF1188D Make Equal

题意

给出(n)个数(a_i),每次操作可以给一个数加上(2^k)。问最少需要多少次操作来使得该数组的每个元素都相等。

(nle 10^5,a_i le 10^{17})

传送门

思路

即求数组(b),使得(a_i+b_i)都相同且(sum ext{bitcount}(b_i))最小

排序差分,最大的数改变(b_n),则(b_i=b_n+(a_n-a_i)),求(b_n)使得,(sum ext{bitcount}(b_n+(a_n-a_i)))最小

然后就变成了一道双倍经验题CF778E

很容易想到记录后一位的进位情况,然后从低位到高位转移,(dp[i][j])表示当前考虑到第(i)位,(j)中为(1)的数后一位有进位,枚举(b_n)当前位上是(0)还是(1),但是这样子状态数是(2^n),思考有没有优化方法。

越大的数越容易进位,在只考虑后面位的时候,也就是按(0 ext{~} 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
",dp[w+1][0]);
}

后记

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

原文地址:https://www.cnblogs.com/flyfeather6/p/12710994.html