Codeforces 1188D Make Equal DP

题意:给你个序列,你可以给某个数加上2的幂次,问最少多少次可以让所有的数相等。

思路(官方题解):我们先给序列排序,假设bit(c)为c的二进制数中1的个数,假设所有的数最后都成为了x, 显然x >= a[n],那么最后的总花费为Σbit(x - a[i])。不妨假设x = t + a[n], b[i] = a[n] - a[i], 那么问题转化为了求Σbit(t + b[i])的最小值。我们假设最后取得最小值的数是x。我们假设已经知道了填充x的低k - 1位的最小花费,我们来考虑填充第k位。我们发现,知道第k位对答案的贡献需要知道以下3项:1:b序列在第k位的二进制位。2:低位的进位。3:这一位x是填0还是填1。对于第一项,我们已经知道了。第三项在dp的时候直接判断转移就行,现在的问题是第二项。乍一看,我们需要2 ^ n个状态来表示上一位的进位情况。仔细想一想发现,因为这一位要么填0,要么填1,即要么不加数,要么加同一个数。所以,我们对所有的数取模 2 ^ k后从小到大排序,最后产生进位的位置是一个后缀,这样我们就可以把状态数降成O(n)个。我们假设dp[i][j]为填x的第i位,上一位产生进位的后缀长度为j的最小花费,转移的时候判断这位填0还是填1就可以了。dp的复杂度是O(n * log(n) * log(max(a)), 为什么是log(max(a))呢?因为t的大小不会大于b[1] = a[n] - a[1]。官方题解有证明,但是我们可以直观感受一下这是对的:当t正好等于b[1]时, 相当于是b[1] << 1, 之后的操作会重复,并且不会使答案更小。

采用基于二进制数的基数排序可以使复杂度进一步降低为O(n * log(max(a))。

代码(基数排序版本):

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 100010;
LL dp[70][maxn];
LL a[maxn], f[maxn], f1[maxn];
int cnt;
int sum0[maxn], sum1[maxn], tot0, tot1, n;
void Sort(LL x) {
	cnt = 0;
	for (int i = 1; i <= n; i++)
		if(((a[f[i]] >> x) & 1) == 0) f1[++cnt] = f[i];
	for (int i = 1; i <= n; i++)
		if((a[f[i]] >> x) & 1) f1[++cnt] = f[i];
	for (int i = 1; i <= n; i++)
		f[i] = f1[i];
}
void init(LL x) {
	for (int i = 1; i <= n; i++) {
		sum0[i] = sum0[i - 1];
		sum1[i] = sum1[i - 1];
		if((a[f[i]] >> x) & 1) sum1[i]++;
		else sum0[i]++;
	}
	tot1 = sum1[n], tot0 = sum0[n];
}
int main() {
	LL mx = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		mx = max(mx, a[i]);
	}
	for (int i = 1; i <= n; i++) {
		a[i] = mx - a[i];
	}
	memset(dp, 0x3f, sizeof(dp));
	for (int i = 1; i <= n; i++) f[i] = i;
	dp[0][0] = 0;
	for (LL i = 0; i < 62; i++) {
		if(i != 0) {
			Sort(i - 1);
		}
		init(i);
		for (int j = 0; j <= n; j++) {
			for (int bit = 0; bit < 2; bit++) {
				if(bit == 0) {
					int val = sum1[n - j] + tot0 - sum0[n - j], Next_state = tot1 - sum1[n - j];
					dp[i + 1][Next_state] = min(dp[i + 1][Next_state], dp[i][j] + val);
				} else if(bit == 1) {
					int val = sum0[n - j] + tot1 - sum1[n - j], Next_state = n - sum0[n - j];
					dp[i + 1][Next_state] = min(dp[i + 1][Next_state], dp[i][j] + val);
				}
			}
		}
	}
	printf("%lld\n", dp[62][0]);
}

官方题解有一些证明,代码也比较易懂。

官方题解:http://codeforces.com/blog/entry/68079

官方题解代码:https://pastebin.com/usq1czKq

转载于:https://www.cnblogs.com/pkgunboat/p/11147353.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
区间DP是一种动态规划的方法,用于解决区间范围内的题。在Codeforces竞赛中,区间DP经常被用于解决一些复杂的字符串或序列相关的题。 在区间DP中,dp[i][j]表示第一个序列前i个元素和第二个序列前j个元素的最优解。具体的转移方程会根据具体的题而变化,但是通常会涉及到比较两个序列的元素是否相等,然后根据不同的情况进行状态转移。 对于区间长度为1的情况,可以先进行初始化,然后再通过枚举区间长度和区间左端点,计算出dp[i][j]的值。 以下是一个示例代码,展示了如何使用区间DP来解决一个字符串匹配的题: #include <cstdio> #include <cstring> #include <string> #include <iostream> #include <algorithm> using namespace std; const int maxn=510; const int inf=0x3f3f3f3f; int n,dp[maxn][maxn]; char s[maxn]; int main() { scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) dp[i][i] = 1; for(int i = 1; i <= n; i++) { if(s[i] == s[i - 1]) dp[i][i - 1] = 1; else dp[i][i - 1] = 2; } for(int len = 3; len <= n; len++) { int r; for(int l = 1; l + len - 1 <= n; l++) { r = l + len - 1; dp[l][r] = inf; if(s[l] == s[r]) dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); else { for(int k = l; k <= r; k++) { dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]); } } } } printf("%d\n", dp[n]); return 0; } 希望这个例子能帮助你理解区间DP的基本思想和应用方法。如果你还有其他题,请随时提
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值