CF1188D
  • 给定 $n$ 个数字 $a_1,a_2,\cdots,a_n$,每次操作可以给某个 $a_i$ 加上 $2$ 的非负整数次幂。
  • 求最少的操作次数使得 $n$ 个数相等。
  • $1\leq n\leq 10^5,0\leq a_i\leq 10^{17}$

sol

  • 不妨先将 $a$ 从小到大排序。
  • 设最后每个数都等于 $a_n+x(x\geq 0)$。那么总代价为 $\sum\limits_{i=1}^n popcount(a_n+x-a_i)$。
  • 现在要找一个 $x$ 使得答案最小。不妨先将 $a_i$ 变为原先的 $a_n-a_i$。
  • 那么现在总代价为 $\sum\limits_{i=1}^n popcount(x+a_i)$。

  • 从低到高考虑每一位,那么 $x+a_i$ 的第 $k$ 位是 $0/1$ 由以下三个条件决定:

  • $x$ 的第 $k$ 位是 $0$ 还是 $1$
  • $a_i$ 的第 $k$ 位是 $0$ 还是 $1$
  • $x+a_i$ 的第 $k-1$ 位有没有向前进位
  • 暴力的做法是从低到高枚举每一位,用 $2^n$ 的状态记录每个数有没有进位,显然复杂度接受不了。
  • 但是可以发现,由于每个 $a_i$ 加的都是 $x$,所以 $a_i$ 的后 $k-1$ 位越大,越可能向第 $k$ 位进位。
  • 所以,“每个数有没有进位”的状态数只有 $O(n)$ 个,每个进位状态为后 $k-1$ 位从小到大排序后的一个后缀。

  • 记 $f[k][i]$ 表示考虑了后 $k$ 位,后 $k$ 位最大的 $i$ 个数进位的最小代价。

  • 如何转移?考虑当前这一位 $x$ 是 $0$ 还是 $1$。
  • 0:
  • 对答案的贡献:“前一位没进位且第 $k$ 位为 $1$ 的数“的个数 + ”前一位进位且第 $k$ 位为 $0$ 的数“的个数($x+a_i$ 在这一位为 $1$);
  • 进位的数量:"前一位进位且第 $k$ 位为 $1$ 的数"的个数。
  • 1:
  • 对答案的贡献:“前一位进位且第 $k$ 位为 $1$ 的数“的个数 + ”前一位没进位且第 $k$ 位为 $0$ 的数“的个数($x+a_i$ 在这一位为 $1$);
  • 进位的数量:$n-$"前一位没进位且第 $k$ 位为 $0$ 的数"的个数。

  • 至于如何求这些“个数”,就记个前缀和,存下后 $k$ 位从小到大前 $i$ 个中没进位数的个数和进位的数的个数即可。

  • 可以用类似于基数排序的方法优化每次的排序,时间复杂度 $O(n \log V)$,其中 $V=\max{a_i}$。

```cpp

include

include

using namespace std;
typedef long long ll;
const ll INF=1ll<<60;
int n,p[510000],sum[2][510000];
int tmp1[510000],tmp2[510000];
ll dp[60][510000],c1,c2,c3,p1,p2,m;
ll sta,a[510000];
/bool cmp(int x,int y){
return (a[x]&sta)<(a[y]&sta);
}
/
int main(){
// freopen("equal.in","r",stdin);
// freopen("equal.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
sort(a+1,a+n+1);
for (int i=1;i<=n;i++) a[i]=a[n]-a[i];
for (int w=0;w<=58;w++)
for (int i=0;i<=n;i++)
dp[w][i]=INF;
for (int i=1;i<=n;i++) p[i]=i;
dp[0][0]=0;
for (int w=0;w<58;w++){
sta=(1ll<<w)-1;
// sort(p+1,p+n+1,cmp);
for (int i=1;i<=n;i++){
sum[0][i]=sum[0][i-1];
sum[1][i]=sum[1][i-1];
sum[(a[p[i]]>>w)&1][i]++;
}
for (int i=0;i<=n;i++){
int tmp=(sum[0][n]-sum[0][n-i])+sum[1][n-i];
int sta=sum[1][n]-sum[1][n-i];
dp[w+1][sta]=min(dp[w+1][sta],dp[w][i]+tmp);
}
for (int i=0;i<=n;i++){
int tmp=(sum[1][n]-sum[1][n-i])+sum[0][n-i];
int sta=n-sum[0][n-i];
dp[w+1][sta]=min(dp[w+1][sta],dp[w][i]+tmp);
}
int cnt1=0,cnt2=0;
for (int i=1;i<=n;i++)
if ((a[p[i]]>>w)&1) tmp1[++cnt1]=p[i];
else tmp2[++cnt2]=p[i];
int k=0;
for (int i=1;i<=cnt2;i++) p[++k]=tmp2[i];
for (int i=1;i<=cnt1;i++) p[++k]=tmp1[i];
}
printf("%lld\n",dp[58][0]);
return 0;
}
```

本文永久更新地址:

https://blog.hydd.cf/p/cf1188d/

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇