题意:有一串密码你需要经常输入,长度为n(1e5)由前m(20)个小写字母组成。你需要买一个键盘即m个字母的排列,当你在输入该密码的时候,你手指(单指打字)移动的总距离最小。移动的距离就是密码每相邻2个字母在键盘上的距离总和。问你最小距离。
思路:
首先预处理d[i][j]为i和j在该密码相邻的次数。
dp[se]表示当集合se在键盘的左侧(不考虑se的顺序,只考虑某字母有没有在se里面)其他不在该集合的字母在右侧且在一个点上。当m为6(即字符集为abcdef时),se为{a,b,c}时,dp[se]代表abc在位置1,2,3上,def全部在4位置上。这样一个键盘的最小总距离。
现在考虑转移方程,在求dp[se]时假设是通过dp[se1]转移而来(size(se1) =size(se)-1);设se为{a,b,c,d},se1为{a,b,c};相当于把d放入集合时对改状态做了多少贡献,即集合se的元素全部左移一位,贡献就是sum(d[i][j])(i代表在se的元素,j即为不在se的元素)。
代码
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdio>
using namespace std;
#define sz(a) a.size()
#define all(x) (x).begin(), (x).end()
#define endl '\n'
#define debug(a) cout<<#a<<": "<<a<<'\n'
#define mod(x) (((x)%MOD+MOD)%MOD)
#define mem(a, b) memset(a,b,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 3e5 + 7;
char str[maxn];
int d[30][30];
int dp[1<<20];
int main()
{
// freopen("in.txt", "r", stdin);
int n,m;
scanf("%d%d",&n,&m); scanf("%s",str);
for(int i = 1; i<n; i++)
d[str[i]-'a'][str[i-1]-'a']++, d[str[i-1]-'a'][str[i]-'a']++;
memset(dp,INF,sizeof(dp)); dp[0] = 0;
for(int se = 1; se<(1<<m); se++)
{
for(int i = 0; i<m; i++) if(se&(1<<i))
dp[se] = min(dp[se^(1<<i)],dp[se]);
for(int i = 0; i<m; i++) if(se&(1<<i))
for(int j= 0; j<m; j++) if(!(se&(1<<j)))
dp[se] += d[i][j];
}
printf("%d\n", dp[(1<<m)-1]);
return 0;
}