题目
题目链接: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) 的价值。方便起见,你只需要输出这个最大的价值即可。
(1leq nleq 2 imes 10^5),(1leq kleq 17)。
思路
这个 (kleq 17) 很明显在提示状压。
二分答案,设 (f[i]) 表示状态 (i) 里面所有字符都已经至少存在一个长度为 (mid) 的子串,最少需要覆盖掉 (s) 前多少个字符。
预处理出 ( ext{nxt}[i][j]) 表示从字符串第 (i) 个字符起,要塞下连续 (mid) 个字符 (j),末尾最小会到达哪里。转移时考虑刷表,枚举不在集合 (i) 中的一个元素 (j),那么
[f'[icup j]=min(f[icup j], ext{nxt}[f[i]+1][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;
}