K e y b o a r d P u r c h a s e Keyboard\ Purchase Keyboard Purchase
Description:
You have a password which you often type — a string s of length n. Every character of this string is one of the first m lowercase Latin letters.
Since you spend a lot of time typing it, you want to buy a new keyboard.
A keyboard is a permutation of the first m Latin letters. For example, if m=3, then there are six possible keyboards: abc, acb, bac, bca, cab and cba.
Since you type your password with one finger, you need to spend time moving your finger from one password character to the next. The time to move from character si to character si+1 is equal to the distance between these characters on keyboard. The total time you have to spend typing the password with a keyboard is called the slowness of this keyboard.
More formaly, the slowness of keyboard is equal to ∑i=2n|possi−1−possi|, where posx is position of letter x in keyboard.
For example, if s is aacabc and the keyboard is bac, then the total time of typing this password is |posa−posa|+|posa−posc|+|posc−posa|+|posa−posb|+|posb−posc| = |2−2|+|2−3|+|3−2|+|2−1|+|1−3| = 0+1+1+1+2=5.
Before buying a new keyboard you want to know the minimum possible slowness that the keyboard can have.
Input:
The first line contains two integers n and m (1≤n≤105,1≤m≤20).
The second line contains the string s consisting of n characters. Each character is one of the first m Latin letters (lowercase).
Output:
Print one integer – the minimum slowness a keyboard can have.
Sample Input:
- 6 3
aacabc
Sample Output:
- 5
Sample Input:
- 6 4
aaaaaa
Sample Output:
- 0
Sample Input:
- 15 4
abacabadabacaba
Sample Output:
- 16
TJ:
神奇的状压DP,枚举子集
首先处理出字符串中所有相邻字符出现的次数,把字母从0~m-1编号,每个字母相当于一个顶点,字符串中的相邻字符相当于连了一条边,连接相邻两个顶点,现在要求给各个顶点赋值(各不相同),两点之间的边权为两点权值之差的绝对值,现在要求找出一种赋值方法,使得所有边权的总和最小。
如何解决这个问题?
我们考虑现在有一个数列,然后从左往右加入各个点,而点赋值为其在数列中的下标。
假设当前数列长度为k,则在此之后加入的点的下标必然比数列中已经存在的点大(废话),由于要求边权的和,放在这个数列中就相当于求两点在数列中的相对距离(r-l+1) x 边的数量,而这个距离如何算呢,考虑现在已经加入的数列的顶点集合为msk(可以用一个二进制数来表示),但是现在不知道msk所表示的集合的排列顺序(即在数列中的顺序),但是没关系,我们把当前数列看成一个点(缩点,设为S),然后由于后面的点和S点的距离至少为1,所以我们把最小的可能距离记下来,即S点和之后所有点的连边数量x1。
我们发现对于这个方法,对于当前枚举的子集,后面再加入点所产生的贡献和当前集合的顺序没有关系了,而当前集合对之后答案的贡献只和当前集合中数的插入的顺序有关,所以只要找出当前集合以什么顺序排列能得到对之后的贡献最少即可,这个只要枚举最后一个插入的数是什么就行了,记录最优值,子集DP。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+7;
const int MAXM = 20;
const int INF = 0x3f3f3f3f;
int f[1<<MAXM],n,m,cnt[MAXM][MAXM];
char s[MAXN];
int main(){
scanf("%d %d %s",&n,&m,s);
for(int i = 1; i < n; i++) {
cnt[s[i]-'a'][s[i-1]-'a']++;
cnt[s[i-1]-'a'][s[i]-'a']++;
}
memset(f,INF,sizeof(f));
f[0] = 0;
for(int msk = 1; msk < (1<<m); msk++) {//枚举子集
int c = 0; //当前集合对之后所有数的最小贡献
for(int i = 0; i < m; i++){
for(int j = i + 1; j < m; j++){
if((msk>>i&1) ^ (msk>>j&1)) c += cnt[i][j];
}
}
for(int i = 0; i < m; i++){ //枚举最后加入的数
if(1<<i&msk) f[msk] = min(f[msk], f[msk^(1<<i)] + c);
}
}
printf("%d\n",f[(1<<m)-1]);
return 0;
}