CF1238E Keyboard Purchase
题意
-
给定
n
和m
,给出一个由m
个不同小写字母组成的长度为n
的字符串 -
可以改变前
m
个小写字母的相对位置 -
每一种可能的值是给出的字符串的每两个相邻位置的字母的相对位置的和
- 即 ∑ ∣ p o s s [ i ] − p o s s [ i + 1 ] ∣ \sum |pos_{s[i]}-pos_{s[i+1]}| ∑∣poss[i]−poss[i+1]∣
-
求最小的一种排列方式
思路
显然可以考虑状压dp
每次转移为下一个加入的字符为哪个
需要考虑的是转移代价的计算
由于i
与j
的代价(
(
p
o
s
i
−
p
o
s
j
)
∗
n
u
m
(
i
,
j
)
(pos_i-pos_j)*num(i,j)
(posi−posj)∗num(i,j))与他加入字符串的时间有关
对于这样的问题,
我们考虑若j
不加入字符串,那么
p
o
s
i
pos_i
posi需要向后推移一位
则代价需要加上j
字符与所有已加入字符串的字符的对数
dp[0] = 0;
int in[21], out[21], val[21];
register int p, q;
for (int i = 0; i < limit; i++) {
int temp = i;
p = q = 0;
for (int j = 0; j < m; j++)
if (temp & n2[j])
in[++p] = j;
else
out[++q] = j;
int ans = dp[temp];
for (int j = 1; j <= q; j++)
for (int k = 1; k <= p; k++)
ans += a[out[j]][in[k]];
int cnt;
for (int j = 1; j <= q; j++) {
cnt = temp | n2[out[j]];
dp[cnt] = min(dp[cnt], ans);
}
}
代码
#include <bits\stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, m;
char s[maxn];
int a[26][26], n2[26];
void init()
{
n2[0] = 1;
for (int i = 1; i < m; i++)
n2[i] = n2[i - 1] << 1;
for (int i = 1; i < n; i++)
if (s[i] != s[i + 1])
a[s[i] - 'a'][s[i + 1] - 'a']++;
for (int i = 0; i < m; i++)
for (int j = i + 1; j < m; j++)
a[i][j] = a[j][i] = a[i][j] + a[j][i];
}
const int maxm = 1 << 20;
int dp[maxm];
int main()
{
scanf("%d%d", &n, &m);
scanf("%s", s + 1);
init();
int limit = (1 << m) - 1;
memset(dp, 0X3f, sizeof(int) * (limit + 1));
dp[0] = 0;
int in[21], out[21], val[21];
register int p, q;
for (int i = 0; i < limit; i++) {
int temp = i;
p = q = 0;
for (int j = 0; j < m; j++)
if (temp & n2[j])
in[++p] = j;
else
out[++q] = j;
int ans = dp[temp];
for (int j = 1; j <= q; j++)
for (int k = 1; k <= p; k++)
ans += a[out[j]][in[k]];
int cnt;
for (int j = 1; j <= q; j++) {
cnt = temp | n2[out[j]];
dp[cnt] = min(dp[cnt], ans);
}
}
printf("%d\n", dp[limit]);
return 0;
}