题目
题意
给一字符串 S S S( m m m种字符),然后你要做一个键盘(一行 m m m列,每个字符用一次),敲打字符串的代价为手移动的距离和( ∑ 1 l e − 1 ∣ p o s [ s [ i ] ] − p o s [ s [ i − 1 ] ] ∣ \sum_{1}^{le-1}|pos[s[i]]-pos[s[i-1]]| ∑1le−1∣pos[s[i]]−pos[s[i−1]]∣, p o s x pos_x posx是 x x x在键盘上的位置)。求敲打出 S S S最少的代价。
思路
我一开始想的是暴力逐位加入字符,然后计算当前已有的
i
i
i个字符的代价,这样需要考虑到前面
i
−
1
i-1
i−1位字符的排列,显然是错的想法。
正解:
答案其实是:
∑
i
=
0
m
a
x
c
h
a
r
∑
j
=
0
m
a
x
c
h
a
r
(
(
p
o
s
[
i
]
−
p
o
s
[
j
]
)
∗
c
n
t
[
i
]
[
j
]
)
\sum_{i=0}^{maxchar} \sum_{j=0}^{maxchar} (\ (pos[i]-pos[j])*cnt[i][j]\ )
∑i=0maxchar∑j=0maxchar( (pos[i]−pos[j])∗cnt[i][j] ),
c
n
t
[
i
]
[
j
]
cnt[i][j]
cnt[i][j]是原串中字符
i
i
i和
j
j
j相邻的个数。
这样,暴力逐位加入字符的时候,考虑所有二元对
<
x
,
y
>
<x,y>
<x,y>,如果
x
x
x是已经确定的字符,
y
y
y是未确定的字符,那么这二元对,就会对答案有贡献
c
n
t
[
x
]
[
y
]
cnt[x][y]
cnt[x][y](相当于距离+1)。
复杂度
O
(
2
m
∗
m
2
)
O(2^m*m^2)
O(2m∗m2)
注意第一层循环枚举的是状态会好些很多。
/* Author : Rshs
* Data : 2019-10-09-00.19
*/
#include<bits/stdc++.h>
using namespace std;
#define FI first
#define SE second
#define LL long long
#define MP make_pair
#define PII pair<int,int>
#define SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-10;
const LL mod = 1e9+7;
const int MXN = 1e6+5;
int mp[30][30];
char s[MXN];
int dp[(1<<21)];
int main(){
int n,m;cin>>n>>m;
scanf("%s",s);
for(int i=1;i<n;i++)
mp[s[i]-'a'][s[i-1]-'a']++,mp[s[i-1]-'a'][s[i]-'a']++;
for(int i=0;i<=(1<<m);i++) dp[i]=INT_MAX/10;
int NN=(1<<m);dp[0]=0;
for(int i=1;i<NN;i++){
for(int j=0;j<m;j++){ //用之前的更新当前的
if( ((1<<j)&i) == 0) continue;
dp[i]=min(dp[i],dp[i-(1<<j)]);
}
for(int j=0;j<m;j++){//暴力枚举二元对
if( ((1<<j)&i) ==0 )continue;
for(int k=0;k<m;k++){
if( ((1<<k)&i) >0) continue;
dp[i]+=mp[j][k];
}
}
}
cout<<dp[(1<<m)-1];
return 0;
}