北邮秋季赛 Confusing Problem
这个题比赛的时候没想出来,当时以为是数位dp,但是状态几乎都没法表示呀,单纯的以为 单独匹配A + 匹配B的 - 匹配A和B的,然后就没有然后了……
赛后看题解说是ac自动机+数位dp,猛然发现用ac自动机来构造状态时再合适不过了,题解是这样说的:
B 数位dp。
状态是dfs(i, m, s, e),i为当前做到前i位,m为是否已经匹配了两个串,s为走到自动机的节点s(或者kmp也能做),e为前i位是否是上限。
但是我敲了,仔细调试发现光有这些状态还是不行的,还要考虑到前缀是否为0,如果为0 ,那么当前i位可以为0,否则就必须从1开始
然后状态表示就是: dp[ pos ][ state] [ match ] [ zero ] 表示当前做到第i位,走到ac自动机state节点,目前是否匹配A或者B,前缀是否为0的个数
#include <cmath> #include <cstdio> #include <iostream> #include <cstring> using namespace std; typedef long long ll; const int NODE=150; int chd[NODE][10],word[NODE],fail[NODE],sz; int dig[50]; ll dp[50][NODE][2][2]; void ins(ll n) { int pos=0; for(;n;n/=10) dig[pos++]=n%10; int p=0; for(int i=pos-1;i>=0;i--) { if(!chd[p][dig[i]]) { memset(chd[sz],0,sizeof(chd[sz])); word[sz]=0; chd[p][dig[i]]=sz++; } p=chd[p][dig[i]]; } word[p]=1; } int Que[NODE]; void ac() { int *s=Que,*e=Que; for(int i=0;i<10;i++) if(chd[0][i]){ *e++=chd[0][i]; fail[chd[0][i]]=0; } while(s!=e) { int p=*s++; for(int i=0;i<10;i++) if(chd[p][i]){ *e++=chd[p][i]; fail[chd[p][i]]=chd[fail[p]][i]; word[chd[p][i]]|=word[fail[chd[p][i]]]; }else chd[p][i]=chd[fail[p]][i]; } } ll dfs(int pos,int ok,int state,bool zero,bool doing) { if(pos<0) return ok; if(!doing&&dp[pos][state][ok][zero]!=-1) return dp[pos][state][ok][zero]; int end=doing?dig[pos]:9; ll ret=0; for(int i=zero?0:1;i<=end;i++) { int newstate=chd[state][i]; ret+=dfs(pos-1,ok|word[newstate],newstate,zero&&!i,doing&&i==end); } if(!doing) dp[pos][state][ok][zero]=ret; return ret; } ll cal(ll n) { int pos=0; for(;n;n/=10) dig[pos++]=n%10; return dfs(pos-1,0,0,1,1); } int main() { ll L,R,A,B; int T; scanf("%d",&T); while(T--) { scanf("%lld%lld %lld%lld",&L,&R,&A,&B); sz=1; memset(chd[0],0,sizeof(chd[0])); ins(A); ins(B); ac(); memset(dp,-1,sizeof(dp)); printf("%lld\n",cal(R)-cal(L-1)); } return 0; }