Educational Codeforces Round 74 E. Keyboard Purchase

https://codeforces.com/contest/1238/problem/E

 

(一开始实在是没想到状压dp)

 

用place[i]表示字母i所在的下标

比如说对于ab两个字符,假如a在前面,那么对答案的贡献应该是 place[b]-place[a]

假如b在前面,对答案的贡献应该是place[a]-place[b]

 

现在更一般化,假如对于a和x两个字符,其中x表示任意除了a之外的字符,那么

假如a在前面,对答案的贡献应该是place[x]-place[a]

假如x在前面,对答案的贡献应该是place[a]-place[x]

 

所以,我们可以从左到右的加入字符,什么意思呢?

对于某个位置i,i之前的的位置已经确定了字符,i之后的位置未确定字符,第i个字符为s[i]

我们使用二进制来表示字符x是否在i前面(已经被确定),1表示在i前面,0表示不在

那么观察上面一般化那个式子

假如此时x为0,那么表示x在s[i]的后面,此时答案减去place[s[i]],然后当我们确定字符x的时候,此时s[i]必然就在x的前面,答案加上place[x],这么一前一后对答案的总贡献就为 place[x]-place[s[i]]],是不是和上面那个式子一样呢

假如此时x为1,只需要当时加上place[s[i]],然后在前面确定x的时候s[i]在x的后面,减去place[x],总贡献就为place[s[i]]-place[x]

所以对于当前位置i,只需要处理当前的place[s[i]]的加减就行了,不需要理会其他

 

因此使用dp[x],其中x为上面所说的二进制,具体看代码

#include<bits/stdc++.h>
#define ll long long 
#define ull unsigned long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxm = 22;
const int maxn = 1e5 + 7;
char inp[maxn];
int cnt[maxm][maxm];
ll dp[(1 << 20)];
int n, m;
int main() {
	cin >> n >> m;
	scanf("%s", inp);
	for (int i = 1; i < n; i++) { //看总共需要加多少次
		if (inp[i] == inp[i - 1]) continue;
		cnt[inp[i] - 'a'][inp[i - 1] - 'a']++;
		cnt[inp[i - 1] - 'a'][inp[i] - 'a']++;
	}
	int end = (1 << m) - 1;
	memset(dp, INF, sizeof(dp));
	dp[0] = 0;
	for (int i = 0; i < end; i++) {
		int num = 0, now = i;
		while (now) {
			now -= now & -now;
			num++; //num: 这个二进制一共有多少个1,num就为当前的位置
		}
		for (int j = 0; j < m; j++) {
			if (!(i & (1 << j))) { //假如二进制中此位为0
				ll sto = dp[i];
				for (int k = 0; k < m; k++) {
					if (i&(1 << k)) {
						sto += (ll)cnt[j][k] * num;
					}
					else
						sto -= (ll)cnt[j][k] * num;
				}
				dp[i | (1 << j)] = min(dp[i | (1 << j)], sto);
			}

		}
	}
	cout << dp[end] << endl;
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值