夫蒟蒻初学数位 dp \texttt{dp} dp,寻水题而得《萌数》,乃谔谔做之。初适,做之悠然。而样例不过,渐生焦躁,AC时已过三,而错之甚奇,乃记之。
萌数
求区间 [ L , R ] [L,R] [L,R] 中有长度大于 2 2 2 的回文子串的数量。
数据范围: 1 ≤ L < R ≤ 1 0 1000 1\le L1≤L<R≤101000 。
数位 dp \texttt{dp} dp 典型题,如果你没学过数位 dp \texttt{dp} dp 就跳进传送门→[传送门]。用记忆化搜索模型,用 DP(n) \texttt{DP(n)} DP(n) 表示 [ 1 , n ] [1,n] [1,n] 区间中的答案, p p p 表示 n n n 的位数, n l i nl_i nli 表示 n n n 从右往左第 i i i 位。
Dfs(int w,int d,int ld,bool free,bool hw) \texttt{Dfs(int~w,int~d,int~ld,bool~free,bool~hw)} Dfs(int w,int d,int ld,bool free,bool hw)
表示:
当前要求从右往左第 w w w 位数。
第 w + 1 w+1 w+1 位数为 d d d。
第 w + 2 w+2 w+2 位数为 l d ld ld。
如果前 p − w p-w p−w 位和 n n n 相同, f r e e = 0 free=0 free=0;否则 f r e e = 1 free=1 free=1。
如果前 p − w p-w p−w 位中已经出现了“长度大于 2 2 2 的回文子串”, f w = 1 fw=1 fw=1;否则 f w = 0 fw=0 fw=0。
Dfs \texttt{Dfs} Dfs 的值表示数的数量。
如果找到的第 w w w 个数字 i i i 满足 i = = d i==d i==d 或 i = = l d i==ld i==ld,就说明有了长度至少为 2 2 2 的回文子串。用 f w , d , l d , h w f_{w,d,ld,hw} fw,d,ld,hw 数字记下答案,完成记忆化搜索。
code
lng Dfs(int w,int d,int ld,bool free,bool hw){
if(!w) return hw;
if(free&&~f[w][d][ld][hw]) return f[w][d][ld][hw];
int up=free?9:nl[w]; lng res=0;
for(int i=0;i<=up;i++)
(res+=Dfs(w-1,i,d,free||i<up,hw||i==d||i==ld))%=mod;
/*
下一位是w-1位,下一个数的上一个数是i,下一个数的上上个数是d。
如果原来就free==1或者i与n的第w位不相同下一个free=1。
如果原来就有了长度为2的回文子串或i,d形成回文子串或i,d,ld形成回文子串,下一个fw=1。
*/
if(free) f[w][d][ld][hw]=res;
return res;
}
所以
DP(n) = ∑ i = 1 p − 1 ∑ j = 1 9 Dfs(i-1,j,10,1,0) + ∑ j = 1 n l p − 1 Dfs(p-1,j,10,1,0) + Dfs(p-1, n l p ,10,0,0) \texttt{DP(n)}=\sum\limits_{i=1}^{p-1}\sum_{j=1}^{9}\texttt{Dfs(i-1,j,10,1,0)}\\ +\sum_{j=1}^{nl_p-1}\texttt{Dfs(p-1,j,10,1,0)}\\ +\texttt{Dfs(p-1,}nl_p\texttt{,10,0,0)} DP(n)=i=1∑p−1j=1∑9Dfs(i-1,j,10,1,0)+j=1∑nlp−1Dfs(p-1,j,10,1,0)+Dfs(p-1,nlp,10,0,0)
。为什么 l d = 10 ld=10 ld=10 ?因为 l d ld ld 还不存在,而且如果你赋值为 0 0 0,会影响 Dfs \texttt{Dfs} Dfs 结果;如果你赋值为 − 1 -1 −1,会在记忆化数组的下标上 RE \color{9d3dcf}\texttt{RE} RE。所以赋值为 10 10 10 是较好选择,然后记忆化数组的下标范围就要开 11 11 11。蒟蒻因为这里赋值成 − 1 -1 −1 而 WA \color{ff0000}\texttt{WA} WA 了两次。
code
lng DP(char*n,lng a){
int p=strlen(n+1); lng res=0;
for(int i=1;i<=p;i++) nl[i]=n[p+1-i]-'0';
nl[1]+=a;
if(p==1&&nl[1]<=0) return 0;
for(int i=1;nl[i]<0&&i<=p;i++)
nl[i]+=10,nl[i+1]--;
while(!nl[p]&&p>1) p--;
// debug(p,nl);
memset(f,-1,sizeof f);
for(int i=1;i<=p-1;i++)
for(int j=1;j<=9;j++)
(res+=Dfs(i-1,j,10,1,0))%=mod;
for(int j=1;j<=nl[p];j++)
(res+=Dfs(p-1,j,10,j<nl[p],0))%=mod;
return res;
}
然后最后答案就是 DP(R) − DP(L-1) \texttt{DP(R)}-\texttt{DP(L-1)} DP(R)−DP(L-1),因为 L L L 和 R R R 远爆 long long \texttt{long long} long long,所以用字符串输入或处理。
#include
using namespace std;
//&Start
#define lng long long
//&Debug
template<class T>
void debug(int x,T*arr){
for(int i=1;i<=x;i++)
cout<<arr[i]<<"\n "[i<x];
}
//&DP
const int W=1010,D=10;
const lng mod=1e9+7;
char L[W],R[W];
int nl[W];
lng f[W][D][D+1][2];
lng Dfs(int w,int d,int ld,bool free,bool hw){
if(!w) return hw;
if(free&&~f[w][d][ld][hw]) return f[w][d][ld][hw];
int up=free?9:nl[w]; lng res=0;
for(int i=0;i<=up;i++)
(res+=Dfs(w-1,i,d,free||i<up,hw||i==d||i==ld))%=mod;
if(free) f[w][d][ld][hw]=res;
return res;
}
lng DP(char*n,lng a){
int p=strlen(n+1); lng res=0;
for(int i=1;i<=p;i++) nl[i]=n[p+1-i]-'0';
nl[1]+=a;
if(p==1&&nl[1]<=0) return 0;
for(int i=1;nl[i]<0&&i<=p;i++)
nl[i]+=10,nl[i+1]--;
while(!nl[p]&&p>1) p--;
// debug(p,nl);
memset(f,-1,sizeof f);
for(int i=1;i<=p-1;i++)
for(int j=1;j<=9;j++)
(res+=Dfs(i-1,j,10,1,0))%=mod;
for(int j=1;j<=nl[p];j++)
(res+=Dfs(p-1,j,10,j<nl[p],0))%=mod;
return res;
}
//&Main
int main(){
scanf("%s %s",L+1,R+1);
printf("%lld\n",(DP(R,0)-DP(L,-1)+mod)%mod);
return 0;
}
祝大家学习愉快!