专栏文章
题解:CF1110H Modest Substrings
CF1110H题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @minb86xr
- 此快照首次捕获于
- 2025/12/01 23:34 3 个月前
- 此快照最后确认于
- 2025/12/01 23:34 3 个月前
压缩 DFA 的好题。但是还挺难写的。代码参考了某篇题解。
首先我们考虑暴力,把 的所有数插入 AC 自动机里边去,转化成多模式串匹配问题(模式串为 所有整数的十进制表示)。设 表示填完前 位,目前在 节点,合法子串数最多是多少。
然后另设辅助转移数组 表示根到 的路径组成的串 上,有多少个后缀(可以包含本身)为模式串。采用后缀的原因是,我们要记录新加的那个字符的贡献。
这个其实就是【模版】AC自动机。等价于 在 fail 树上的所有祖先中,是模式串结尾的节点个数。这个也很好理解,直接用 fail 的定义就行( 指向的节点 代表一个最长的模式串的前缀,使其是 所代表的串的后缀)。求这个东西直接递推就行。
考虑优化。朴素 DP 会在 中插入所有数字,状态量极大。观察到很多子树在本质上是「满 10 叉树」,可以被整体压缩。
我们类比数位 DP 的思想:当构造数时,只要前面的部分已经“脱离上界限制”,那么后续各位都可以自由取值。这类情况可以用一对参数 来描述:
- :表示固定的前缀;
- :表示后续有 位可以自由填。
这样一来,我们就可以只在压缩后的 DFA(相当于“数位状态自动机”)上进行转移,而不用显式展开所有数字。
我们考虑在压缩之后,通过计算“一定可以的贡献”来拼出答案。注意这里 的转移不变。
我们先说怎么做,然后说为什么。
在压缩后的自动机上,我们需要考虑「当前节点后续还能接 个自由位」时的最大匹配贡献。
对于每个节点 ,若其后能接续 个自由位,则「一定会产生贡献」的部分,就是所有满足条件的 组合:
- 是某个模式串的结尾(或者是某个参数 的结尾),并且是当前节点代表串的后缀;
- 是自由位个数,且 。
由于无论后面具体走的 步怎么填,这些模式串都会出现,因此我们可以一次性累加这些确定的贡献:
那这个为啥是对的呢?我们可以将所有自由位上可能出现的情况分为两类讨论:
-
形如 的串(其中 是某个模式串的结尾并且是当前节点所代表串的某个后缀)。这类情形里,前缀 已经构成一个匹配(以当前位置为结尾的匹配已经成立),因此不论后续自由位如何填充,匹配的这部分贡献都是必然发生的。我们将这类“必然发生的贡献”在预处理阶段计入相应的数组(记为
g[node][*]的确定项),并在 DP 的转移时直接加上。 -
形如 的串(若干完全由自由位组成的段)。这类串的前缀在当前节点并未与任何模式串匹配,上述的“必然发生”情形并不适用;它们是否产生匹配、产生多少匹配,取决于自由位的具体取值。这部分贡献不能事先以“必然项”完全归约到单个常数,而是需要通过在压缩 DFA 上的后续转移(即 DP 中按字符展开或通过
g[node][len]的“可选/扩展”语义)来累积统计。换言之,预处理可把其中某些较为确定的累积项(例如长度小于等于某值时必然触发的模式)合并,但大部分依赖填法的贡献是在 DP 的转移过程中被计算(或通过g[node][len]的正确定义间接体现)。
我们可以可考虑用 表示这个贡献,转移类似原来辅助数组的转移,不过是加了一维。
但如何算初始时有哪些形如 的对也是一个问题。这个直接用 的定义,枚举长度(等于 ,在 ,等于 ),和在哪一位分离,分讨即可。具体地,对当前走到的节点 ,我们把 的可以创造分离的儿子创建形如 的对,在 就直接从根创建即可。注意分讨 的情况。
转移时,我们尝试所有数字字符 ,并利用自动机转移函数 与自由位贡献 更新状态:
其中 ,即剩余自由位上限, 为自动机接收 后会跳到哪里。
最终答案为所有 中的最大值。
为了得到字典序最小的最优解,直接从 的最优解开始倒退,看他们能从哪里来。无需显式减出 dag,从 开始,选能到最优解且字典序最小的解即可。
CPP#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;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...