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