北邮秋季赛 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;
}