[Codeforces1238E]Keyboard Purchase

题意

一个只包含前 m m m个字母的长度为 n n n的字符串 s s s

对于一个包含前 m m m个字母的排列,设 p o s c pos_c posc表示字母 c c c在排列中的位置

求一个排列使得 ∑ 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=2nposs[i]poss[i1]最小,输出最小值即可


题解

c n t x , y cnt_{x,y} cntx,y表示字母 x , y x,y x,y有多少次相邻(特别的 c n t x , x = 0 cnt_{x,x}=0 cntx,x=0

那么就是最小化 ∑ i = 1 m ∑ j = 1 i − 1 c n t i , j ∣ p o s i − p o s j ∣ \sum_{i=1}^m\sum_{j=1}^{i-1}cnt_{i,j}|pos_i-pos_j| i=1mj=1i1cnti,jposiposj

注意到 m m m只有 20 20 20,可以考虑状压,设全集为 T T T

对于一个集合 s s s,假设新加入一个字母 x x x,我们就把 x x x放在排列的 ∣ s ∣ |s| s位(由于是绝对值的差,全员的位置都 − 1 -1 1不影响结果)

可以证明这样做是能取到所有最优情况的,因为扩展到集合 s s s时, s s s中每个字母放在最后的情况都会被算到

那么把 x x x加入集合的代价为 ∑ y ∈ s c n t y , x ( ∣ s ∣ − p o s y ) \sum_{y\in s}cnt_{y,x}(|s|-pos_y) yscnty,x(sposy)

( ( (注意此时是无法暴力存下位置然后枚举转移的,因为可能会有很多总代价相同的排列,时间复杂度可以达到 O ( m 3 2 m + n ) O(m^32^m+n) O(m32m+n) ) ) )

s u m s , x = ∑ y ∈ s c n t y , x sum_{s,x}=\sum_{y\in s}cnt_{y,x} sums,x=yscnty,x,这个是可以预处理出来的

主要考虑后面减去的 p o s y pos_y posy如何转化

而由于我们无法存下位置,所以只能在 x x x上面想如何处理

假设再新加入一个字母 z z z,则其代价中与 x x x有关的就只有 − c n t z , x p o s x -cnt_{z,x}pos_x cntz,xposx这一项

而此时 p o s x = ∣ s ∣ pos_x=|s| posx=s,其中 z ∈ T − s z\in T-s zTs

所以对于所有的 z z z x x x的贡献和就为 − ∣ s ∣ × s u m T − s , x -|s|\times sum_{T-s,x} s×sumTs,x

所以 ∑ y ∈ s c n t x , y ( ∣ s ∣ + 1 − p o s y ) \sum_{y\in s}cnt_{x,y}(|s|+1-pos_y) yscntx,y(s+1posy)就可以等价转化为 ∣ s ∣ × ( s u m s , x − s u m T − s , x ) |s|\times(sum_{s,x}-sum_{T-s,x}) s×(sums,xsumTs,x)

于是设 f s f_s fs表示集合 s s s的等价代价的最小和,那么

f s ∣ x = min ⁡ x ∉ s f s + ∣ s ∣ × ( s u m s , x − s u m T − s , x ) f_{s|x}=\min_{x\notin s}f_{s}+|s|\times(sum_{s,x}-sum_{T-s,x}) fsx=x/sminfs+s×(sums,xsumTs,x)

时间复杂度 O ( m 2 m + n ) O(m2^m+n) O(m2m+n)

#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=1e5+5,M=25,S=(1<<21)+5;
typedef long long ll;
int n,m,T,Mi[M],cnt[M][M];
int f[S],Log[S],cntBit[S],sumC[S][M];
char s[N];
int main(){
	#ifndef ONLINE_JUDGE
		file("s");
	#endif
	scanf("%d %d\n",&n,&m);
	gets(s+1);T=(1<<m)-1;
	fp(i,2,n)
		++cnt[s[i]-'a'][s[i-1]-'a'],
		++cnt[s[i-1]-'a'][s[i]-'a'];
	fp(i,0,m)cnt[i][i]=0;
	Mi[0]=1;Log[0]=-1;
	fp(i,1,m-1)Mi[i]=Mi[i-1]<<1;
	fp(s,1,T){
		Log[s]=Log[s>>1]+1;
		cntBit[s]=cntBit[s>>1]+(s&1);
		int y=Log[s&(-s)];
		fp(x,0,m-1)sumC[s][x]=sumC[s^Mi[y]][x]+cnt[x][y];
	}
	memset(f,127,sizeof f);f[0]=0;
	fp(s,0,T)fp(i,0,m-1)if(!(s&Mi[i]))
		cmin(f[s|Mi[i]],f[s]+cntBit[s]*(sumC[s][i]-sumC[T^s][i]));
	printf("%d\n",f[T]);
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值