Codeforces 1804H - Code Lock(状压 dp)

发布时间 2023-04-29 22:40:27作者: tzc_wk

对于一种排列方案,答案显然等于相邻字符在环上对应的劣弧长度之和。

然后其实你可能会想到很多状压 / 折半搜索方法,包括但不限于枚举一半的信息然后折半搜后一半,但稍加思考会发现这些方案都避不开记录元素之间的相对顺序,而但凡涉及到这一点,复杂度都是阶乘起步。因此我们只能另辟蹊径。

考虑 \(k\) 是偶数的情况。对于 \(i\)\(i\bmod k\) 位置之间的弧,考虑与其距离 \(\dfrac{k}{2}\) 的那段弧,有一个非常重要的性质是,考察这两段弧将圆周分成的两部分,假设两部分中的字母集合分别是 \(S,T\),那么这两个弧被经过的次数之和等于 \(\sum\limits_{x\in S}\sum\limits_{y\in T}c_{x,y}+c_{y,x}\),其中 \(c_{x,y}=\sum\limits_{i=2}^n[s_{i-1}=x][s_i=y]\)。这样我们考虑枚举 \(1\)\(\dfrac{k}{2}+1\) 将圆周分成的两部分对应的字符集合 \(S,T\),然后顺着环一遍 DP 即可。转移的时候比较 naive 的方法是枚举一个属于 \(S\) 但不在当前状态中的点 \(x\),和一个属于 \(T\) 且在当前状态中的点 \(y\),删除 \(x\) 并加入 \(y\),但这样是 \(k^2\) 的,被卡掉了。优化方法是类似于设计辅助数组 \(g\),先删除 \(S\) 元素时执行转移 \(f\to g\),加入 \(T\) 元素时执行转移 \(g\to f\),这样转移可以降到 \(O(k)\)。这样总复杂度是 \(\dbinom{k}{k/2}^2·k\),好像刚好可以卡过去。

对于 \(k\) 是奇数的情况也是类似的,只不过要对所有 \(i\in[1,k]\) 都算一遍贡献然后除以 \(2\)

实际实现并不用记两个辅助数组,可以直接通过 __builtin_parity 判断是 \(f\) 还是 \(g\),具体细节见代码。

const int MAXN=16;
const int MAXL=1e5;
const int MAXP=65536;
const int INF=0x3f3f3f3f;
int n,len,c[MAXN+3][MAXN+3],coef[MAXP+5];char s[MAXL+5];
struct dat{
	int mn;ll num;
	dat(){mn=num=0;}
	dat(int _mn,ll _num){mn=_mn;num=_num;}
}dp[MAXP+5],ans;
dat operator +(dat a,dat b){
	if(a.mn!=b.mn)return (a.mn<b.mn)?a:b;
	else return dat(a.mn,a.num+b.num);
}
dat inc(dat a,int v){return dat(a.mn+v,a.num);}
int main(){
	scanf("%d%d%s",&n,&len,s+1);
	for(int i=2;i<=len;i++)if(s[i]!=s[i-1])c[s[i-1]-'a'][s[i]-'a']++;
	for(int i=1;i<(1<<n);i++)for(int j=0;j<n;j++)if(i>>j&1)
		for(int k=0;k<n;k++)if(~i>>k&1)coef[i]+=c[j][k]+c[k][j];
	ans=dat(INF,0);
	for(int s=1;s<(1<<n);s+=2)if(__builtin_popcount(s)==(n+1)/2){
		for(int i=0;i<(1<<n);i++)dp[i]=(i==1)?dat(coef[s],1):dat(INF,0);
		for(int t=1;t<(1<<n);t++)if(dp[t].num){
			if(__builtin_parity(t)){
				for(int i=0;i<n;i++){
					if((s>>i&1)==0&&(t>>i&1)==0){
						int nxt=t^(1<<i);
						if(n&1)dp[nxt]=dp[nxt]+inc(dp[t],coef[s^t]);
						else dp[nxt]=dp[nxt]+dp[t];
					}
				}
			}else{
				for(int i=0;i<n;i++){
					if((s>>i&1)==1&&(t>>i&1)==0){
						int nxt=t^(1<<i);
						dp[nxt]=dp[nxt]+inc(dp[t],coef[s^t]);
					}
				}
			}
		}ans=ans+dp[(1<<n)-1];
	}printf("%d\n%lld\n",ans.mn/(1+n%2)+len,ans.num);
	return 0;
}