2018-3-22模拟考试 癞皮狗买彩票

Posted on 2018-03-29 22:53  SirKnight  阅读(108)  评论(0)    收藏  举报

在ac自动机上dp,dpij0表示到第i位ac自动机上第j个节点前面严格小于n的方案数,转移时能到0至9,1是与n相等的方案数,转移到比后一位小的0,和等于后一位的1,如果当前碰到在集合中的串了,就不转移。为了防止前导0,每一位从根向外++,最后统计答案就是dpl-1i0和dpl-1i1的总和(不包括集合中的串)。

#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;

#define ll long long int

const int mod=1e9+7;

struct node
{
	ll nxt[10];
	bool flag;
	ll fail;
}tree[1501];

char n[1201],s[1501];
ll dp[1201][1501][2];
ll l,m,cnt=0;

void insert(ll len)
{
	ll i,x=0;
	for(i=0;i<len;i++)
	{
		if(!tree[x].nxt[s[i]-'0'])
			tree[x].nxt[s[i]-'0']=++cnt;
		x=tree[x].nxt[s[i]-'0'];
	} 
	tree[x].flag=1;
}

void getfail()
{
	queue<ll>q;
	ll i;
	for(i=0;i<=9;i++)
	{
		if(tree[0].nxt[i])
			q.push(tree[0].nxt[i]);
	}
	while(!q.empty())
	{
		ll x=q.front();
		q.pop();
		for(i=0;i<=9;i++)
		{
			if(tree[x].nxt[i])
			{
				tree[tree[x].nxt[i]].fail=tree[tree[x].fail].nxt[i];
				if(tree[tree[tree[x].fail].nxt[i]].flag==1)//如果fail指针指向的节点有flag,它也必须有 
					tree[tree[x].nxt[i]].flag=1;
				q.push(tree[x].nxt[i]);
			}
			else
				tree[x].nxt[i]=tree[tree[x].fail].nxt[i];//ac自动机的精髓,必须加 
		}
	}
}

int main()
{
	scanf("%s",n); 
	l=strlen(n);
	scanf("%lld",&m);
	ll i;
	for(i=1;i<=m;i++)
	{
		scanf("%s",s);
		ll len=strlen(s);
		insert(len);
	}
	getfail();
	for(i=1;i<n[0]-'0';i++)
		dp[0][tree[0].nxt[i]][0]++;
	dp[0][tree[0].nxt[n[0]-'0']][1]++;
	for(i=0;i<=l-2;i++)
	{
		ll j;
		for(j=1;j<=9;j++)
			dp[i+1][tree[0].nxt[j]][0]++;//前面都是0的情况,保证不重 
		for(j=0;j<=cnt;j++)
		{
			if(tree[j].flag)
				continue;
			ll k;
			for(k=0;k<=9;k++)
				dp[i+1][tree[j].nxt[k]][0]=(dp[i+1][tree[j].nxt[k]][0]+dp[i][j][0])%mod; 
			for(k=0;k<n[i+1]-'0';k++)
				dp[i+1][tree[j].nxt[k]][0]=(dp[i+1][tree[j].nxt[k]][0]+dp[i][j][1])%mod;
			dp[i+1][tree[j].nxt[n[i+1]-'0']][1]=(dp[i+1][tree[j].nxt[n[i+1]-'0']][1]+dp[i][j][1])%mod;  
		}
	}
	ll ans=0; 
	for(i=0;i<=cnt;i++)
	{
		if(!tree[i].flag)
			ans=(ans+dp[l-1][i][0]+dp[l-1][i][1])%mod;
	}
	printf("%lld\n",ans%mod);
	return 0;
}