codeforces1238E Keyboard Purchase

题目链接

题目:

给定一个长度为 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=2npossipossi1,其中 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) (1n105,1m20)

题解:

字符集的范围不是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={psp=c(sp1=csp+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 pneighborcpossp<poscposcpneighborcpossp>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=minxmsk(dpmskx+pneighborcspmskposxpneighborcsp/mskposx)
我们可以 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=pneighborch1sp=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=xmskax,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=minxmsk(dpmskx+costmskx,x×posx(neighborxcostmskx,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=costmskminbitmsk,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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值