CF1188D Make Equal

一、题目

点此看题

二、解法

首先把要求的东西翻译一下,其实就是找到一个 \(x\geq 0\),使得下列式子最小:

\[\sum cnt(x+\max a-a_i) \]

其中 \(cnt(x)\) 表示 \(x\) 二进制位中 \(1\) 的个数,为了方便我们使 \(a_i\leftarrow\max a-a_i\)

如果 \(dp\) 求这个式子的最小值那么肯定是按位规划,我们考虑以下信息:

  • \(x\) 这一位规划 \(0/1\) 的代价(转移方法)
  • 所有 \(a_i\) 在这一位出现情况(已知条件)
  • \(a_i\) 是否在之前的规划中出现过进位(状态)

现在问题就是进位的状态数可能达到 \(O(2^n)\),但是考虑如果只保留前 \(i-1\) 位,越大的 \(a_i\) 越有可能进位。我们把所有 \(a_i\) 按此从小到大排序,那么发现进位的一定是 \(a\) 的一个前缀

\(dp[i][j]\) 为考虑到第 \(i\) 位,有 \(j\) 个数发生进位的最小代价,时间复杂度 \(O(n\log n\log a)\)

三、总结

要想着怎么套用动态规划模型,比如此题你就不能按数字规划(请问你这样规划了个啥)

考虑有效状态是 \(dp\) 优化的常见方式。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,mx,ans,a[M],s[2][M],f[70][M];
int cmp(int x,int y)
{
	return (x&((1ll<<k)-1))<(y&((1ll<<k)-1));
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		mx=max(mx,a[i]); 
	}
	for(int i=1;i<=n;i++)
		a[i]=mx-a[i];
	memset(f,0x3f,sizeof f);
	f[0][0]=0;ans=1e18;
	for(k=0;k<60;k++)
	{
		sort(a+1,a+1+n,cmp);
		for(int i=1;i<=n;i++)
		{
			s[0][i]=s[0][i-1];s[1][i]=s[1][i-1];
			s[(a[i]>>k)&1][i]++;
		}
		for(int i=0;i<=n;i++)
		{
			int w,p;
			w=s[1][n-i]+s[0][n]-s[0][n-i],p=s[1][n]-s[1][n-i];
			f[k+1][p]=min(f[k+1][p],f[k][i]+w);
			w=s[0][n-i]+s[1][n]-s[1][n-i];p=n-s[0][n-i];
			f[k+1][p]=min(f[k+1][p],f[k][i]+w);
		}
	}
	printf("%lld\n",f[60][0]);
}
posted @ 2021-07-26 16:48  C202044zxy  阅读(302)  评论(0编辑  收藏  举报