题目
题目链接:http://codeforces.com/problemset/problem/1550/E
设 \(s\) 是一个由前 \(k\) 个小写字母构成的字符串,\(v\) 是前 \(k\) 个小写字母中的某一个。定义 \(\mathrm{MaxLen}(s,v)\) 表示 \(s\) 所有仅由字母 \(v\) 构成的连续子串的最长长度。
定义 \(s\) 的价值为所有 \(\mathrm{MaxLen}(S,v)\) 的最小值,其中 \(v\) 取遍前 \(k\) 个小写字母。
现在给定一个长度为 \(n\) 的字符串 \(s\),\(s\) 中字母要么是前 \(k\) 个小写字母中的某一个,要么是问号。你需要将 \(s\) 中的每一个问号替换成前 \(k\) 个小写字母中的一个,并最大化 \(s\) 的价值。方便起见,你只需要输出这个最大的价值即可。
\(1\leq n\leq 2\times 10^5\),\(1\leq k\leq 17\)。
思路
这个 \(k\leq 17\) 很明显在提示状压。
二分答案,设 \(f[i]\) 表示状态 \(i\) 里面所有字符都已经至少存在一个长度为 \(mid\) 的子串,最少需要覆盖掉 \(s\) 前多少个字符。
预处理出 \(\text{nxt}[i][j]\) 表示从字符串第 \(i\) 个字符起,要塞下连续 \(mid\) 个字符 \(j\),末尾最小会到达哪里。转移时考虑刷表,枚举不在集合 \(i\) 中的一个元素 \(j\),那么
最后判断一下 \(f[2^m-1]\) 是否 \(\leq n\) 即可。
时间复杂度 \(O((nk+2^kk)\log n)\)。
代码
/*
#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
*/
#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=200010,M=19;
int n,m,lim,cnt[N][M],nxt[N][M],f[1<<M];
char s[N];
int main()
{
scanf("%d%d%s",&n,&m,s+1);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
cnt[i][j]=cnt[i-1][j]+(s[i]-‘a‘+1==j);
int l=0,r=n/m,mid;
while (l<=r)
{
memset(f,0x3f3f3f3f,sizeof(f));
memset(nxt,0x3f3f3f3f,sizeof(nxt));
f[0]=0; lim=(1<<m);
mid=(l+r)>>1;
for (int i=n-mid+1;i>=1;i--)
{
int c=0;
for (int j=1;j<=m;j++)
if (cnt[i+mid-1][j]-cnt[i-1][j])
{
if (c) { c=-1; break; }
c=j;
}
for (int j=1;j<=m;j++)
if (!c || c==j) nxt[i][j]=i+mid-1;
else nxt[i][j]=nxt[i+1][j];
}
for (int i=0;i<lim;i++)
for (int j=1;j<=m;j++)
if (!(i&(1<<j-1)) && f[i]<=n && nxt[f[i]+1][j]<=n)
f[i|(1<<j-1)]=min(f[i|(1<<j-1)],nxt[f[i]+1][j]);
if (f[lim-1]<=n) l=mid+1;
else r=mid-1;
}
cout<<l-1;
return 0;
}