题目:
思路:
先预处理出所有的字符的贡献对,例如 abcd,则贡献对为(a,b),(b,c),(c,d) 均为一个.
如果在新建盘中一个字符 位置 pos , 则与其有关的贡献对中,编号比他大的对中就要减去它,即为 - pos, 反之就是加上它,即为 +pos 。
可以看出我们可以把 单个字符的贡献单独放出来算,所以我们考虑直接枚举顺序,每次维护最小值.
而状压dp转移的过程中恰好可以将顺序一一枚举
Ac_Code:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <stack>
#include <bitset>
#include <vector>
#include <map>
#include <string>
#include <cstring>
#define fir first
#define sec second
using namespace std;
const int maxn = (1<<20)+7;
const int INF = 1e9+7;
int n,m;
string s;
long long dp[maxn];
long long val[maxn][25];
int vis[maxn];
int len[maxn];
int bit[25];
int mn[30][30];
int all[30];
int main() {
cin>>n>>m>>s;
bit[0] = 1;
for(int i=1;i<=m;i++) bit[i] = bit[i-1]*2;
for(int i=1;i<n;i++) {
int d = s[i]-'a';
int dd = s[i-1]-'a';
if(d == dd) continue;
mn[dd][d]++;
mn[d][dd]++;
all[d]++;
all[dd]++;
}
int up = (1<<m);
for(int i=0;i<up;i++) dp[i] = INF,dp[0] = 0;
for(int i=0;i<up;i++) {
for(int j=0;j<m;j++) {
if(bit[j]&i) continue;
int res = (i|bit[j]);
dp[res] = min(dp[res],dp[i] + (2*val[i][j]-all[j])*(len[i]+1));
if(vis[res] == 0) {
for(int l=0;l<m;l++) {
val[res][l] = val[i][l] + mn[j][l];
}
len[res] = len[i]+1;
}
vis[res] = 1;
}
}
printf("%lld\n", dp[up-1]);
return 0;
}