Keyboard Purchase
洛古地址
一
看
数
据
范
围
就
知
道
是
状
压
d
p
,
具
体
地
,
d
p
[
S
]
表
示
已
填
的
集
合
为
S
的
最
小
价
值
,
然
后
我
们
加
一
个
数
的
贡
献
就
是
前
面
的
贡
献
和
对
后
面
的
贡
献
一看数据范围就知道是状压dp,具体地,dp[S]表示已填的集合为S的最小价值,然后我们加一个数的贡献就是前面的贡献和对后面的贡献
一看数据范围就知道是状压dp,具体地,dp[S]表示已填的集合为S的最小价值,然后我们加一个数的贡献就是前面的贡献和对后面的贡献
因
为
最
后
是
贡
献
求
和
,
所
以
可
以
对
每
个
点
单
独
算
贡
献
即
∑
x
,
y
∈
(
1
,
n
)
p
o
s
[
x
]
−
p
o
s
[
y
]
(
p
o
s
[
x
]
>
p
o
s
[
y
]
)
=
∑
i
=
1
n
s
u
m
1
i
∗
p
o
s
[
x
]
−
s
u
m
2
i
∗
p
o
s
[
x
]
因为最后是贡献求和,所以可以对每个点单独算贡献即\sum_{x,y∈(1,n) pos[x]-pos[y]}(pos[x]>pos[y])=\sum_{i=1}^n sum1_i*pos[x]-sum2_i*pos[x]
因为最后是贡献求和,所以可以对每个点单独算贡献即∑x,y∈(1,n)pos[x]−pos[y](pos[x]>pos[y])=∑i=1nsum1i∗pos[x]−sum2i∗pos[x]
s
u
m
1
指
i
前
面
的
个
数
,
s
u
m
2
指
i
后
面
的
个
数
sum1指i前面的个数,sum2指i后面的个数
sum1指i前面的个数,sum2指i后面的个数
P.S 洛古上有一些优化
#include
#include
#include
using namespace std;
const int M=21,N=1e5+5,INF=123456789;
int dp[(1<<M)];
int cnt[25][25],n,m;
char s[N];//这里是N,不是M,调了很久
int main(){
scanf("%d%d",&n,&m);
scanf("%s",(s+1));
int len=strlen(s+1);
for(int i=2;i<=len;i++){
cnt[s[i-1]-‘a’+1][s[i]-‘a’+1]++;
cnt[s[i]-‘a’+1][s[i-1]-‘a’+1]++;
}
for(int i=1;i<=m;i++) cnt[i][i]=0;/不然的话后面计算时会加上cnt[i][i],从而导致答案为负/
memset(dp,0x7f,sizeof(dp));
dp[0]=0;
for(int S=0;S<(1<<m);S++){
int summ=0,T=(S^((1<<m)-1));
bool ex[M];
for(int x=1;x<=m;x++){
if(S&(1<<(x-1))){
ex[x]=1;
summ++;
}
else ex[x]=0;
}
for(int x=1;x<=m;x++){
if(ex[x]) continue;
int tmpsum=0;
for(int y=1;y<=m;y++){
if(!ex[y])
{
tmpsum-=cnt[x][y];
}
if(ex[y]){
tmpsum+=cnt[x][y];
}
}
dp[S|(1<<(x-1))]=min(1lldp[S|(1<<(x-1))],dp[S]+1ll(summ+1)*(tmpsum));
}
}
printf("%d",dp[(1<<m)-1]);
}
检查是要先检查数组范围