CF1110H Modest Substrings 题解

· · 题解

本题统计的是 n 位数中(允许前导零)在 [l,r] 之间的子串计数的最大值及方案。

计数这个子串相当于是计数字典序在 l,r 之间的子串。

一个 naive的 想法是把 [l,r] 间的串全塞到 AC 自动机里,然后 dp ,考虑节点 i 对答案的贡献 g[i] 。这个贡献除了 i 代表的串自身产生的贡献,还应包括 fail[i] 的贡献(因为它同时也出现了)。也就是说,g[i] 应该是节点 ifail 能跳到的点的贡献总和。(也许是 AC 自动机 dp 常规操作?)

g[i] 很简单,就是插入时标记串的贡献,然后在 getfail 的时候每次查到一个后继节点就 g[to]+=g[fail[to]] 就行。

继续这个暴力的思路,考虑设计 dp,状态 dp[rt][len] 表示在节点 rt 上组成的 len 位数方案数。显然可以拿 dp[rt][len]+g[son] 去更新 dp[son][len+1] ,答案就是 \max \{ dp[i][n]\}

这个TLE自动机+ dp 方法效率低下,我们考虑优化它。

这时 $g[i][len]$ 的的处理方式不变,转移变成了用 $dp[rt][len]+\sum \limits ^{n-len-1} _ {k=0} g[son][k]$ 更新 $dp[son][len+1]$ 。所以需要处理出 $g[son]$ 的前缀和,然后 $dp$ 即可。 现在剩下一个问题:如何按上面要求建出$trie$树。 用类似数位 $dp$ 的思想,需要分以下几种情况: + $l,r$ 长度相等: 1.当前节点是 $l,r$ 共同前缀:下一节点为终止点,当且仅当下一点数值 $num \in (l_i,r_i)$ 。 2.当前节点只是 $l$ 前缀:要求下一点数值 $num \in (l_i,9]$ 。 3.当前节点只是 $r$ 前缀:要求下一点数值 $num \in [0,r_i)$ 。 4.当前节点是 $l$ 或 $r$ 终点,对当前点记录贡献,即 $g[rt][0]++$. + $l,r$ 长度不等: 情况 $2,3,4$同上。 还需要额外添加一种情况:对于 $len \in (len_l,len_r)$的串,显然都是符合题意的,因此要对每一个 $g[son[0]][len-1]$ 记录贡献。 这样建树就完成了,$getfail$ 和原来几乎没有变化,只需要顺便处理下 $g$ 数组。 $dp$ 直接顺序枚举长度和当前节点,递推即可。每个数位产生至多十个新节点,故总复杂度大概是 $O(|\Sigma|^2N\log R)$,$R$为值域 $10^{800}$,$|\Sigma|=10$.(显然跑不满) ~~完结撒花~~ 哦对,还要求字典序最小的方案。 先枚举所有 $dp[rt][n]$ 找到等于答案的节点并标记,再从后向前推。找到所有能到达答案的路径,再按字典序最小的方式遍历输出即可。 有一些小细节,看代码吧。 ```cpp #include<bits/stdc++.h> #define for_each(i,bg,ed) for(int i=bg;i<ed;++i) using namespace std; const int M=2e4+10,N=2e3+10,SIG=10; int ch[M][SIG],fail[M], //AC自动机部分 g[M][N], //g[u][len]:节点 u 处后接长度为 len 的自由数码的方案数。 dp[M][N], //dp[u][pos]:匹配到 pos 处,在节点 u 上的匹配数。 cnt,n,len_l,len_r,ans; string sl,sr,str; inline void Read(){ cin>>sl>>sr>>n; len_l=sl.size(); len_r=sr.size(); for_each(i,0,len_l)sl[i]-='0'; for_each(i,0,len_r)sr[i]-='0'; } inline void CreateNode(int rt,int son_id){if(!ch[rt][son_id])ch[rt][son_id]=++cnt;} //判断并新建节点 inline void RegMsg(int rt,int l,int r,int len){ for_each(nxt,l,r){ CreateNode(rt,nxt); g[ch[rt][nxt]][len]++; } }//对每个儿子记录信息 inline void MoveIter(int &rt,int num){ CreateNode(rt,num); rt=ch[rt][num]; }//移动根节点。 //处理一个g数组,省略可以自由安排的部分,以减少AC自动机节点数。 void BuildTree(){ int rt_l=0,rt_r=0; if(len_l==len_r){ //Case A:l,r长度相等 for_each(i,1,len_l+1){ int num_l=sl[i-1],num_r=sr[i-1]; if(rt_l==rt_r){ //case 1:当前节点属于l,r共同前缀 RegMsg(rt_l,num_l+1,num_r,len_l-i); MoveIter(rt_l,num_l); MoveIter(rt_r,num_r); }else{ //case 2:分别处理l,r前缀。 RegMsg(rt_l,num_l+1,10,len_l-i); RegMsg(rt_r,0,num_r,len_r-i); MoveIter(rt_l,num_l); MoveIter(rt_r,num_r); } } g[rt_l][0]++; if(rt_l!=rt_r)g[rt_r][0]++; }else{ //Case B:l,r长度不等 for_each(i,1,len_l+1){ //处理 l int num_l=sl[i-1]; RegMsg(rt_l,num_l+1,10,len_l-i); MoveIter(rt_l,num_l); } for_each(i,1,len_r+1){ //处理 r int num_r=sr[i-1]; RegMsg(rt_r,0,num_r,len_r-i); MoveIter(rt_r,num_r); } g[rt_l][0]++,g[rt_r][0]++; for_each(i,len_l+1,len_r) //处理长度介于 len_l 和 len_r 的部分。 for_each(nxt,1,10){ if(!ch[0][nxt])ch[0][nxt]=++cnt; g[ch[0][nxt]][i-1]++; } } ch[0][0]=0; } queue<int> q; void GetFail(){ for(int i=0;i<SIG;++i) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()){ int it=q.front(); q.pop(); for(int i=0;i<SIG;++i){ int &to=ch[it][i]; if(to){ q.push(to),fail[to]=ch[fail[it]][i]; for_each(i,0,n+1)g[to][i]+=g[fail[to]][i];//继承fail树父亲的g值。 }else to=ch[fail[it]][i]; } } for_each(rt,0,cnt+1) for_each(i,0,n+1) g[rt][i]+=g[rt][i-1]; } void GetAns(){ memset(dp,-1,sizeof(dp)); dp[0][0]=0; for_each(i,0,n+1) for_each(rt,0,cnt+1) if(dp[rt][i]>=0){ dp[rt][i]+=g[rt][n-i]; for_each(nxt,0,10) dp[ch[rt][nxt]][i+1]=max(dp[rt][i],dp[ch[rt][nxt]][i+1]); } for_each(rt,0,cnt+1)ans=max(ans,dp[rt][n]); cout<<ans<<endl; } bool reg[M][N];//记录方案 void GetSolution(){ for_each(rt,0,cnt+1) if(dp[rt][n]==ans) reg[rt][n]=1; for(int len=n-1;~len;len--) for_each(rt,0,cnt+1) if(dp[rt][len]>=0) for_each(nxt,0,10) if(reg[ch[rt][nxt]][len+1]&&dp[ch[rt][nxt]][len+1]==dp[rt][len]+g[ch[rt][nxt]][n-len-1]){ reg[rt][len]=1; break; } int rt=0; for_each(len,0,n) for_each(nxt,0,10) if(reg[ch[rt][nxt]][len+1]&&dp[ch[rt][nxt]][len+1]==dp[rt][len]+g[ch[rt][nxt]][n-len-1]){ putchar('0'+nxt); rt=ch[rt][nxt]; break; } } int main(){ Read(); BuildTree(); GetFail(); GetAns(); GetSolution(); } ```