[CF1550E]Stringforces
壹、题目描述 ¶
贰、题解 ¶
真的是一道妙妙题。 关键在于二分答案之后利用贪心,在 \(\rm DP\) 上进行转移。
首先考虑二分答案 \(d\),进行检测时,由于 \({\Large|}\Sigma{\Large|}\) 很小,我们考虑状压,那么一个很显然的一维状态就是 \(f(s)\) 表示当前字符满足情况为 \(s\),但是值里面应该记录什么呢?我们不妨挖掘题目性质:
这个题目显然是存在贪心思路的,先不在意那些已经有东西的位置,我们显然是很想让填完了我们的目标字符集后,剩下的位置尽量多 —— 剩得多,才有无限可能嘛,也即,尽可能让我们填进去的字符占的位置尽量靠左(下标尽量小)。
那么,现在状态完整了,\(f(s)\) 表示填完目标字符集后的右端点最小是多少,如果 \(f(U)\le n\),那么当前二分答案合法。
考虑如何进行转移,显然我们如果要填一个字符,肯定都是连着一次性填完,那么,对于一个位置 \(i\),如果 \([i,i+d-1]\) 中除了我们想要填的字符 \(c\) 以及空格以外没有别的地方,那么我们就可以将字符 \(c\) 填入,那么如何快速判断?
考虑定义状态 \(g(i,c)\) 表示自位置 \(i\) 以后,第一个 能放下 \(c\) 的位置的右端点是多少,转移其实很简单,从后往前考虑一个位置 \(i\),如果 \([i,i+d-1]\) 没有别的字符(这一个问题我们可以通过存储每个字符最后一次出现的位置在哪里来解决),那么 \(g(i,c)=i\),否则 \(g(i,c)=g(i+1,c)\).
预处理 \(\mathcal O(nk)\),而 \(\rm DP\) 部分 \(\mathcal O(2^k)\),总复杂度即为 \(\mathcal O(nk\log n+2^k\log n)\).
叁、参考代码 ¶
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
namespace Elaina{
#define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
#define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define mmset(a, b) memset(a, b, sizeof a)
// #define int long long
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
template<class T>inline T fab(T x){ return x<0? -x: x; }
template<class T>inline void getmin(T& x, const T rhs){ x=min(x, rhs); }
template<class T>inline void getmax(T& x, const T rhs){ x=max(x, rhs); }
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>inline void writc(T x, char s='\n'){
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
}
using namespace Elaina;
const int maxn=2e5;
const int maxk=17;
char s[maxn+5];
int n, k, U;
inline void input(){
n=readin(1), k=readin(1), U=1<<k;
scanf("%s", s);
}
inline bool check(int d){
vector<int>lst(k, n);
vector< vector<int> >pos(n+1, vector<int>(k, n+1));
for(int i=n-1; i>=0; --i){
if(s[i]!='?') lst[s[i]-'a']=i;
int cur=n;
for(int j=0; j<k; ++j){
if(i+d>cur) pos[i][j]=pos[i+1][j];
else pos[i][j]=i+d;
cur=min(cur, lst[j]);
}
cur=n;
for(int j=k-1; j>=0; --j){
if(i+d>cur) pos[i][j]=pos[i+1][j];
cur=min(cur, lst[j]);
}
}
vector<int>f(U, n+1);
f[0]=0;
for(int i=0; i<U; ++i) if(f[i]<n+1){
for(int j=0; j<k; ++j) if(!((i>>j)&1))
getmin(f[i^(1<<j)], pos[f[i]][j]);
}
return f[U-1]<=n;
}
signed main(){
input();
int l=0, r=n, mid, ans=0;
while(l<=r){
mid=(l+r)>>1;
if(check(mid)) ans=mid, l=mid+1;
else r=mid-1;
}
writc(ans);
return 0;
}
肆、关键之处 ¶
设计 \(\rm DP\) 状态时应当考虑最佳转移策略,根据这个策略我们想出方法进行维护。