[SDOI2014]数数
题解:
做过ac自动机上dp的这题应该就很容易想到了
首先在ac自动机上搞dp
表示当前考虑了i位,在自动机的j位上
然后转移就可以了
考虑限制
显然是一个数位dp
考虑位数小于n显然满足要求
考虑位数等于n
令f[i][j][0/1]表示前i位,自动机j上,与限制是否重合 然后枚举转移就行了
另外就是对于位数比它少的再做一次(否则的话有前导零可能会让答案变小)
代码:
#include <bits/stdc++.h> using namespace std; #define N 2000 #define mo 1000000007 char cc[N]; int x1[N],x2[N],c[5000][11],val[5000],fail[5000]; int dp[1300][5000][2],cnt; bool tt=1; void insert(char *cc) { int len=strlen(cc),now=0; for (int i=0;i<len;i++) { int v=cc[i]-'0'; if (!c[now][v]) c[now][v]=++cnt; now=c[now][v]; } val[now]=1; } queue<int> q; void build() { for (int i=0;i<=9;i++) if (c[0][i]) fail[c[0][i]]=0,q.push(c[0][i]); while (!q.empty()) { int u=q.front(); q.pop(); for (int i=0;i<=9;i++) { if (c[u][i]) { fail[c[u][i]]=c[fail[u]][i]; q.push(c[u][i]); } else c[u][i]=c[fail[u]][i]; val[c[u][i]]|=val[c[fail[u]][i]]; } } } void plus2(int &x,int y) { x+=y; x=x%mo; } bool pd(int x,int y) { if(x==0&&y==0) return(false); else return(true); } int main() { freopen("noi.in","r",stdin); freopen("noi.out","w",stdout); std::ios::sync_with_stdio(false); cin>>cc; int n,m,len=strlen(cc); n=len; for (int i=0;i<len;i++) x1[i]=cc[i]-'0'; cin>>m; for (int i=1;i<=m;i++) { cin>>cc; insert(cc); } build(); dp[0][0][0]=1; for (int i=0;i<=n-1;i++) for (int j=0;j<=cnt;j++) // if (dp[i][j]) { for (int k=0;k<=9;k++) if (!val[c[j][k]]&&(pd(i,k))) plus2(dp[i+1][c[j][k]][1],dp[i][j][1]); for (int k=0;k<=x1[i]-1;k++) if (!val[c[j][k]]&&(pd(i,k))) plus2(dp[i+1][c[j][k]][1],dp[i][j][0]); if (!val[c[j][x1[i]]]) plus2(dp[i+1][c[j][x1[i]]][0],dp[i][j][0]); } int ans=0; for (int i=0;i<=cnt;i++) plus2(ans,dp[n][i][0]),plus2(ans,dp[n][i][1]); memset(dp,0,sizeof(dp)); dp[0][0][0]=1; for (int i=0;i<=n-2;i++) for (int j=0;j<=cnt;j++) { for (int k=0;k<=9;k++) if (!val[c[j][k]]&&(pd(i,k))) plus2(dp[i+1][c[j][k]][0],dp[i][j][0]); } for (int i=1;i<=n-1;i++) for (int j=0;j<=cnt;j++) plus2(ans,dp[i][j][0]); cout<<ans; return 0; }