Codeforces 1550E Stringforces 题解 [ 蓝 ] [ 状压 dp ] [ 二分 ]

Stringforces:不懂了,为啥这么一道纯靠堆 trick 堆出来的唐氏状压能把全机房难住啊,感觉 CF 评的 *2500 都评高了。

思路

首先观察到求的是最大化最小值,所以显然具有单调性,我们考虑二分这个最小值。

那么如何判断每个数是否都能到这个最小值呢?我们首先可以设计一个很暴力的 dp:定义 dpi,j 表示考虑到第 i 位,已经达到最小值的字符集是 j 是否可行。

转移是显然的,但是显然这个 dp 也是会 T 飞的,我们继续观察,这个 dp 式子的值显然只能有两个:0,1。所以说明这个 dp 的值域很小,我们考虑交换 dp 数组两维的 trick,把可行性放到状态定义里,把 i 作为 dp 的值。

于是现在的 dp 定义就是:dpj 表示已经达到最小值的字符集是 j 的情况下,最小的 i 的值。为啥是最小的 i 呢?因为在 i 最小的时候就能给后面留更多的位置来拼凑出答案。

所以我们再定义一个 sufi,j 表示 i 后面第一个能形成长度为 midj 字母形成的连续段的结尾位置,通过在二分的时候预处理出来,然后跑状压 dp 转移即可。

转移方程为:dpj=mincjsufdps+1,c。其中 cj 表示 j 在二进制下为 1 的位是 cs 表示 j 在去掉第 c 位的 1 后的状态。

那么二分合法的条件就是最终所有字母都满足的情况下的 dp 值 n

时间复杂度 O(2nklogn)

上面的转移方程用的是填表法,代码里写了刷表法。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=200005;
int n,m,suf[N][20],dp[200005];
char c[N];
void init(int len)
{
	memset(suf,0x3f,sizeof(suf));
	for(int i=0;i<m;i++)
	{
		int now=0;
		for(int j=n-len+2;j<=n;j++)now+=(!(c[j]=='?'||c[j]-'a'==i));
		for(int j=n-len+1;j>=1;j--)
		{
			now+=(!(c[j]=='?'||c[j]-'a'==i));
			suf[j][i]=suf[j+1][i];
			if(now==0)suf[j][i]=j+len-1;
			now-=(!(c[j+len-1]=='?'||c[j+len-1]-'a'==i));
		}
	}
}
bool check(int len)
{
	if(len>n)return 0;
	init(len);
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	for(int i=0;i<(1<<m);i++)
	{
		int cur=dp[i];
		if(cur>=n)continue;
		for(int j=0;j<m;j++)
		{
			if(((i>>j)&1)==0)
			{
				int v=(i^(1<<j));
				dp[v]=min(dp[v],suf[cur+1][j]);
			}
		}
	}
	return (dp[(1<<m)-1]<=n);
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m>>c+1;
	int l=0,r=200005,mid;
	while(l<r)
	{
		mid=(l+r+1)>>1;
		if(check(mid))l=mid;
		else r=mid-1;
	}
	cout<<l;
	return 0;
}
posted @   KS_Fszha  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示