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