E. Keyboard Purchase
题意
给定一个长度为 n n n 的字符串 s s s 仅由前 m m m 个小写字母组成
现在要求求出包含前 m m m 个小写字母的键盘,使得在键盘上敲出 s s s 要移动的距离最短
移动总距离为: ∑ i = 2 n ∣ p o s s i − 1 − p o s s i ∣ \sum_{i = 2}^{n} | pos_{s_{i - 1}} - pos_{s_{i}}| ∑i=2n∣possi−1−possi∣
p o s s i pos_{s_i} possi 是 s i s_i si 在键盘上的位置
思路
由于 m m m 很小,我们考虑在键盘上跑状压 D P DP DP,如果当前已经排好了集合 S S S 中的那些位,枚举当前的字母的话,假设当前枚举的不在 S S S 中的字母为 i i i,那么我们就需要找到 s s s 中和字母 i i i 相邻的那些字母 j j j,因为它们相邻,所以要 j → i j \rightarrow i j→i 或 i → j i \rarr j i→j 移动,这个移动是在键盘上移动的。我们不妨以 c n t [ i ] [ j ] cnt[i][j] cnt[i][j] 来表示原 s s s 中相邻的 i − j i-j i−j 配对数量
那么这一位放 i i i 的贡献为:
- ∑ j ∈ S c n t [ j ] [ i ] × ( p o s i − p o s j ) \sum_{j \in S} cnt[j][i] \times (pos_i - pos_j) ∑j∈Scnt[j][i]×(posi−posj)
- ∑ j ∉ S c n t [ j ] [ i ] × ( p o s j − p o s i ) \sum_{j \notin S} cnt[j][i] \times (pos_j - pos_i) ∑j∈/Scnt[j][i]×(posj−posi)
对于 j j j 是否已经在 S S S 中分开考虑,已经在 S S S 中的 j j j,后放置的 i i i 的 p o s i pos_i posi 一定大于 p o s j pos_j posj,反之也很容易得出相应结论。
那么我们把贡献中包含小标 i i i 的提取出来,就变成了只和 i i i 相关的贡献:
- ∑ j ∈ S c n t [ j ] [ i ] × p o s i \sum_{j \in S} cnt[j][i] \times pos_i ∑j∈Scnt[j][i]×posi
- ∑ j ∉ S c n t [ j ] [ i ] × ( − p o s i ) \sum_{j \notin S} cnt[j][i] \times (-pos_i) ∑j∈/Scnt[j][i]×(−posi)
这就是 i i i 的贡献了,可以发现:把所有字母的贡献加起来,一定是这个放置顺序的答案
我们只需要从贡献这个角度切入就可以很容易得出状压 D P DP DP 的转移
时间复杂度: O ( m 2 ⋅ 2 m ) O(m^2 \cdot 2^m) O(m2⋅2m)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
const int M = 21;
int cnt[M][M];
int dp[1 << M];
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::string s;
std::cin >> s;
fore(i, 1, n){
++cnt[s[i] - 'a'][s[i - 1] - 'a'];
++cnt[s[i - 1] - 'a'][s[i] - 'a'];
}
memset(dp, INF, sizeof(dp));
dp[0] = 0;
fore(S, 0, 1 << m)
fore(i, 0, m)
if(!(S >> i & 1)){
int nS = S | 1 << i;
int res = 0;
int pos = __builtin_popcount(S);
fore(j, 0, m){
if(i == j) continue;
if(S >> j & 1) res += cnt[i][j] * pos;
else res -= cnt[i][j] * pos;
}
dp[nS] = std::min(dp[nS], dp[S] + res);
}
std::cout << dp[(1 << m) - 1];
return 0;
}