题解:CF1110H Modest Substrings
压缩 DFA 的好题。但是还挺难写的。代码参考了某篇题解。
首先我们考虑暴力,把
然后另设辅助转移数组
这个其实就是【模版】AC自动机。等价于
考虑优化。朴素 DP 会在
我们类比数位 DP 的思想:当构造数时,只要前面的部分已经“脱离上界限制”,那么后续各位都可以自由取值。这类情况可以用一对参数
这样一来,我们就可以只在压缩后的 DFA(相当于“数位状态自动机”)上进行转移,而不用显式展开所有数字。
我们考虑在压缩之后,通过计算“一定可以的贡献”来拼出答案。注意这里
我们先说怎么做,然后说为什么。
在压缩后的自动机上,我们需要考虑「当前节点后续还能接
对于每个节点
由于无论后面具体走的
那这个为啥是对的呢?我们可以将所有自由位上可能出现的情况分为两类讨论:
-
形如
s+[0,9]^l 的串(其中s 是某个模式串的结尾并且是当前节点所代表串的某个后缀)。这类情形里,前缀s 已经构成一个匹配(以当前位置为结尾的匹配已经成立),因此不论后续自由位如何填充,匹配的这部分贡献都是必然发生的。我们将这类“必然发生的贡献”在预处理阶段计入相应的数组(记为g[node][*]的确定项),并在 DP 的转移时直接加上。 -
形如
[0,9]^l 的串(若干完全由自由位组成的段)。这类串的前缀在当前节点并未与任何模式串匹配,上述的“必然发生”情形并不适用;它们是否产生匹配、产生多少匹配,取决于自由位的具体取值。这部分贡献不能事先以“必然项”完全归约到单个常数,而是需要通过在压缩 DFA 上的后续转移(即 DP 中按字符展开或通过g[node][len]的“可选/扩展”语义)来累积统计。换言之,预处理可把其中某些较为确定的累积项(例如长度小于等于某值时必然触发的模式)合并,但大部分依赖填法的贡献是在 DP 的转移过程中被计算(或通过g[node][len]的正确定义间接体现)。
我们可以可考虑用
但如何算初始时有哪些形如
转移时,我们尝试所有数字字符
其中
最终答案为所有
#include<bits/stdc++.h>
#define _for(i,x,y) for(int i=x;i<=y;++i)
#define _forp(i,x,y,z) for(int i=x;i<=y;i+=z)
#define _rep(i,x,y) for(int i=x;i<y;++i)
using namespace std;
const int N=2005,Node=17005,M=805,V=9;
string L,R;
int lenl,lenr,n;
int f[N][Node];
int g[Node][N];
int son[Node][V+5];
int fail[Node];
int node;
bool vis[N][Node];
int ans=0;
int LIM;
int T(char c){
return (int)(c-'0');
}
void CLEAR(int rt,int num){
son[rt][num]=0;
}
void upd_son(int root,int l,int r,int len){
if(l>r) return ;
if(len<0) return;
_for(i,l,r){
if(!son[root][i]) son[root][i]=++node;
g[son[root][i]][len]++;
}
}
void first_build_G(){
int pl=0,pr=0;
if(lenl==lenr){
_rep(i,0,lenl){
if(pl==pr){
//前缀相等
upd_son(pl,T(L[i])+1,T(R[i])-1,lenl-i-1);
if(!son[pl][T(L[i])]) son[pl][T(L[i])]=++node;
if(!son[pr][T(R[i])]) son[pr][T(R[i])]=++node;
pl=son[pl][T(L[i])];
pr=son[pr][T(R[i])];
}else{
upd_son(pl,T(L[i])+1,9,lenl-i-1);
upd_son(pr,0,T(R[i])-1,lenl-i-1);
if(!son[pl][T(L[i])]) son[pl][T(L[i])]=++node;
if(!son[pr][T(R[i])]) son[pr][T(R[i])]=++node;
pl=son[pl][T(L[i])];
pr=son[pr][T(R[i])];
}
}
if(L!=R){
g[pl][0]++;
g[pr][0]++;
// cout<<'!';
}else g[pl][0]++;
CLEAR(0,0);
}else{
//在 (|L|,|R|)
_for(len,lenl+1,lenr-1){
upd_son(0,1,9,len-1);
}
//|L|
_rep(i,0,lenl){
upd_son(pl,T(L[i])+1,9,lenl-i-1);
if(!son[pl][T(L[i])]) son[pl][T(L[i])]=++node;
pl=son[pl][T(L[i])];
}
//|R|
_rep(i,0,lenr){
upd_son(pr,0,T(R[i])-1,lenr-i-1);
if(!son[pr][T(R[i])]) son[pr][T(R[i])]=++node;
pr=son[pr][T(R[i])];
}
g[pl][0]++;
g[pr][0]++;
CLEAR(0,0);
}
queue<int> q;
_for(i,1,V) if(son[0][i]) q.push(son[0][i]),fail[son[0][i]]=0;
while(!q.empty()){
int top=q.front();
q.pop();
_for(i,0,V){
if(son[top][i]){
q.push(son[top][i]);
fail[son[top][i]] = son[fail[top]][i];
_for(j,0,LIM) g[son[top][i]][j]+=g[fail[son[top][i]]][j];
}else{
son[top][i]=son[fail[top]][i];
}
}
}
}
void build_final_f(){
// _for(i,0,node){
// _for(j,1,n){
// cout<<g[i][j]<<' ';
// }
// cout<<'\n';
// }
_for(i,0,node) _for(j,1,LIM)
g[i][j]+=g[i][j-1];
_for(i,0,n) _for(j,0,node) f[i][j]=-0x3f3f3f3f;
f[0][0]=0;
_for(i,1,n){
_for(j,0,node){
if(f[i-1][j]>=0){
_for(c,0,9){
int nxt=son[j][c];
// cout<<i<<':'<<nxt<<'\n';
// cout<<g[nxt][n-i]<<'\n';
int tail=min(n-i,LIM);
f[i][nxt]=max(f[i-1][j]+g[nxt][tail],f[i][nxt]);
ans=max(ans,f[i][nxt]);
}
}
}
}
cout<<ans<<'\n';
}
void solve(){
memset(son,0,sizeof(son));
memset(fail,0,sizeof(fail));
memset(g,0,sizeof(g));
node=0;ans=0;
cin>>L>>R>>n;
lenl=L.size();
lenr=R.size();
int maxTail = (lenl==lenr ? lenl-1 : lenr-1);
LIM = min(n, maxTail);
first_build_G();//不算前缀和,只算单点
build_final_f();
_for(i,n,n){
_for(j,0,node){
if(f[i][j]==ans){
vis[i][j]=1;
}
}
}
for(int i=n-1;i>=0;--i){
_for(j,0,node){
if(f[i][j]>=0){
_for(c,0,9){
int nxt=son[j][c];
int tail=min(n-i-1,LIM);
if(f[i][j]+g[nxt][tail]==f[i+1][nxt]&&vis[i+1][nxt]){
vis[i][j]=1;
}
}
}
}
}
int nw=0;
_for(i,0,n-1){
_for(c,0,9){
int nxt=son[nw][c];
int tail=min(n-i-1,LIM);
if(f[i][nw]+g[nxt][tail]==f[i+1][nxt]&&vis[i+1][nxt]){
nw=nxt;
cout<<(char)('0'+c);
break;
}
}
}
}
signed main(){
// freopen("test.in","r",stdin);
// freopen("test.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
int T=1;
// cin>>T;
while(T--){
solve();
}
return 0;
}