题目:
给定一个长度为
n
n
n的由前
m
m
m个小写字母组成的字符串
s
s
s,要你给出一个长度为
m
m
m的一维键盘(前
m
m
m个小写字母中的每一个字符占一个键),定义一个键盘的
s
l
o
w
n
e
s
s
slowness
slowness值为
∑
i
=
2
n
∣
p
o
s
s
i
−
p
o
s
s
i
−
1
∣
\sum_{i=2}^n |pos_{s_{i}}-pos_{s_{i-1}}|
∑i=2n∣possi−possi−1∣,其中
p
o
s
c
pos_c
posc为字符
c
c
c在键盘中的位置。问能达到的最小的
s
l
o
w
n
e
s
s
slowness
slowness值为多少。
(
1
≤
n
≤
1
0
5
,
1
≤
m
≤
20
)
(1 \le n \le 10^5,1 \le m \le 20)
(1≤n≤105,1≤m≤20)
题解:
字符集的范围不是26而是20就提醒我们要用状压DP了。
首先
s
l
o
w
n
e
s
s
slowness
slowness值定义里有绝对值不好算贡献,所以将其去掉,那么对于每种字符
c
c
c,会有两种贡献,一种是与比
p
o
s
c
pos_c
posc小的字符产生的,这种情况下字符
c
c
c单独产生的贡献为
p
o
s
c
pos_c
posc;另一种是与比
p
o
s
c
pos_c
posc大的字符产生的,这种情况下字符
c
c
c单独产生的贡献为
−
p
o
s
c
-pos_c
−posc 。更形式化的说,令
n
e
i
g
h
b
o
r
c
=
{
p
∣
s
p
≠
c
∧
(
s
p
−
1
=
c
∨
s
p
+
1
=
c
)
}
neighbor_c=\{p|s_p \ne c \land (s_{p-1}=c \lor s_{p+1}=c) \}
neighborc={p∣sp=c∧(sp−1=c∨sp+1=c)} ,字符
c
c
c的贡献为
∑
p
∈
n
e
i
g
h
b
o
r
c
∧
p
o
s
s
p
<
p
o
s
c
p
o
s
c
−
∑
p
∈
n
e
i
g
h
b
o
r
c
∧
p
o
s
s
p
>
p
o
s
c
p
o
s
c
\sum_{p \in neighbor_c \land pos_{s_p}<pos_c} pos_c-\sum_{p \in neighbor_c \land pos_{s_p}>pos_c} pos_c
∑p∈neighborc∧possp<poscposc−∑p∈neighborc∧possp>poscposc
定义
d
p
m
s
k
dp_{msk}
dpmsk为确定了键盘上的前
∣
m
s
k
∣
|msk|
∣msk∣个位置的字符,字符集为
m
s
k
msk
msk时这些确定位置的字符对
s
l
o
w
n
e
s
s
slowness
slowness值产生的最小贡献。那么转移方程为
d
p
m
s
k
=
m
i
n
x
∈
m
s
k
(
d
p
m
s
k
−
x
+
∑
p
∈
n
e
i
g
h
b
o
r
c
∧
s
p
∈
m
s
k
p
o
s
x
−
∑
p
∈
n
e
i
g
h
b
o
r
c
∧
s
p
∉
m
s
k
p
o
s
x
)
dp_{msk}=min_{x \in msk} (dp_{msk-x}+\sum_{p \in neighbor_c \land s_p \in msk} pos_x - \sum_{p \in neighbor_c \land s_p \notin msk} pos_x )
dpmsk=minx∈msk(dpmsk−x+p∈neighborc∧sp∈msk∑posx−p∈neighborc∧sp∈/msk∑posx)
我们可以
O
(
n
)
O(n)
O(n)预处理出
a
c
h
1
,
c
h
2
=
∑
p
∈
n
e
i
g
h
b
o
r
c
h
1
∧
s
p
=
c
h
2
a_{ch1,ch2}=\sum_{p \in neighbor_{ch1} \land s_p=ch2}
ach1,ch2=∑p∈neighborch1∧sp=ch2,然后
O
(
2
m
×
m
)
O(2^m \times m)
O(2m×m)预处理出
c
o
s
t
m
s
k
,
c
=
∑
x
∈
m
s
k
a
x
,
c
cost_{msk,c}=\sum_{x \in msk} a_{x,c}
costmsk,c=∑x∈mskax,c,那么转移方程可以变为
d
p
m
s
k
=
m
i
n
x
∈
m
s
k
(
d
p
m
s
k
−
x
+
c
o
s
t
m
s
k
−
x
,
x
×
p
o
s
x
−
(
∣
n
e
i
g
h
b
o
r
x
∣
−
c
o
s
t
m
s
k
−
x
,
x
)
×
p
o
s
x
)
dp_{msk}=min_{x \in msk} (dp_{msk-x}+cost_{msk-x,x} \times pos_x -(|neighbor_x|-cost_{msk-x,x}) \times pos_x )
dpmsk=minx∈msk(dpmsk−x+costmsk−x,x×posx−(∣neighborx∣−costmsk−x,x)×posx)
可以实现
O
(
1
)
O(1)
O(1)的转移
由于从小到大枚举
m
s
k
msk
msk的时候可以保证
m
s
k
msk
msk的子集比
m
s
k
msk
msk先枚举到,所以
d
p
dp
dp顺序的正确性可以保证。
关于预处理出
c
o
s
t
m
s
k
,
c
cost_{msk,c}
costmsk,c,一种方式是枚举完
m
s
k
msk
msk和
c
c
c之后,再枚举一遍
m
s
k
msk
msk中的元素
x
x
x累加
a
x
,
c
a_{x,c}
ax,c算出结果,这样复杂度是
O
(
m
2
×
2
m
)
O(m^2 \times 2^m)
O(m2×2m),另一种方式是用利用子集计算出的结果进行递推,具体的来说,先处理出每一个
m
s
k
msk
msk中最小的元素
m
i
n
b
i
t
m
s
k
minbit_{msk}
minbitmsk(其实不是最小的也行),然后
c
o
s
t
m
s
k
,
c
=
c
o
s
t
m
s
k
−
m
i
n
b
i
t
m
s
k
,
c
+
a
m
i
n
b
i
t
m
s
k
,
c
cost_{msk,c}=cost_{msk-minbit_{msk},c}+a_{minbit_{msk},c}
costmsk,c=costmsk−minbitmsk,c+aminbitmsk,c,这种方式的复杂度为
O
(
m
×
2
m
)
O(m \times 2^m)
O(m×2m)
复杂度: O ( n + m × 2 m ) O(n+m \times 2^m) O(n+m×2m)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;
#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=1e5+5;
const int maxm=20;
int n,m;
int dp[1<<maxm],a[maxm][maxm],cnt[maxm],b[1<<maxm][maxm],minbit[1<<maxm],numbit[1<<maxm];
char s[maxn];
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d%d%s",&n,&m,s+1);
for(int i=1;i<=n;i++){
if(i!=1&&s[i]!=s[i-1]){
a[s[i]-'a'][s[i-1]-'a']++;
cnt[s[i]-'a']++;
}
if(i!=n&&s[i]!=s[i+1]){
a[s[i]-'a'][s[i+1]-'a']++;
cnt[s[i]-'a']++;
}
}
for(int i=1;i<(1<<m);i++){
minbit[i]=-1;
for(int j=0;j<m;j++){
if((i>>j)&1){
if(minbit[i]==-1)minbit[i]=j;
numbit[i]++;
}
}
}
for(int i=1;i<(1<<m);i++){
for(int j=0;j<m;j++){
int c=minbit[i];
b[i][j]+=b[i^(1<<c)][j]+a[c][j];
}
}
memset(dp,INF,sizeof(dp));
dp[0]=0;
for(int i=1;i<(1<<m);i++){
for(int j=0;j<m;j++){
if((i>>j)&1){
int t=i^(1<<j);
int v=dp[t]+b[t][j]*numbit[i]-(cnt[j]-b[t][j])*numbit[i];
dp[i]=min(dp[i],v);
}
}
}
printf("%d\n",dp[(1<<m)-1]);
return 0;
}