[SDOI2014][JZOJ3624]数数

本文介绍了一种结合数位动态规划与AC自动机的算法,用于解决一类特殊的数论问题:计算不超过特定数值的所有正整数中,作为字符串表示时不包含预定义字符串集中的任何子串的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

求不大于N的正整数中,看作字符串(不包含前缀0)后,没有子串属于给定字符串集S的数的个数。
1N<101200,|S|100,sS|s|1500


题目分析

不大于某个数,然后对于数字的某些位有特殊要求,这是经典的数位dp模型。
那如何解决子串的约束条件呢?可以发现约束条件相当于多串匹配。那我们考虑就S集合建立AC自动机,然后通过确定当前状态在自动机内的指针,来确定状态是否合法。
在建立自动机,处理fail数组时,我们同时处理布尔数组markx,表示节点x及其fail指针指向节点(可以指多次)中是否有标记为单词的。
我们从高位往低位(字符串前往后)枚举,设fi,j,0..1表示枚举到第i位,当前状态在自动机内的j节点,当前组成的数小于(等于)N的方案数。
我们枚举i的合法状态向i+1转移,状态合法当且仅当markjfalsefi,j,0可以转移到fi+1,nextj,k,0(k[0,9])fi,j,1可以转移到fi+1,nextj,Ni+1,1fi+1,nextj,k,0(k[0,Ni+1))。其中Ni表示N看成字符串后第i位的数值。
注意不能有前导0,但是仍需考虑不足数位的情况。所以我们还可以在每次i转移到i+1时,顺便转移fi+1,next[root][k],0,也就是这一位开始有大于0的数。
N共有n位,自动机节点数为tot,最后答案固然为i<=toti=0fn,i,0+fn,i,1(marki=false)
时间复杂度即为O(n×tot)


代码实现

作者闲得蛋疼,开了滚动数组,空间大跳楼。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;

const int P=1000000007;
const int N=1200;
const int L=1500;

int f[2][L+1][2];
queue<int> Q;
char n[N+1];
int d,ans,m;

const int W=10;

char str[L+1];

struct AC_automation
{
    int fail[L+1],next[L+1][W];
    bool mark[L+1];
    int tot,root;

    int newnode()
    {
        fail[tot]=-1;
        for (int i=0;i<W;i++)
            next[tot][i]=-1;
        mark[tot]=false;
        return tot++;
    }

    void init()
    {
        tot=0;
        root=newnode();
    }

    void insert()
    {
        int l=strlen(str),rt=root;
        for (int i=0;i<l;i++)
            if (next[rt][str[i]-'0']!=-1)
                rt=next[rt][str[i]-'0'];
            else
            {
                next[rt][str[i]-'0']=newnode();
                rt=next[rt][str[i]-'0'];
            }
        mark[rt]=true;
    }

    void build()
    {
        for (int i=0;i<W;i++)
            if (next[root][i]==-1)
                next[root][i]=root;
            else
            {
                fail[next[root][i]]=root;
                Q.push(next[root][i]);
            }
        while (!Q.empty())
        {
            int rt=Q.front();
            Q.pop();
            mark[rt]|=mark[fail[rt]];
            for (int i=0;i<W;i++)
                if (next[rt][i]==-1)
                    next[rt][i]=next[fail[rt]][i];
                else
                {
                    fail[next[rt][i]]=next[fail[rt]][i];
                    Q.push(next[rt][i]);
                }
        }
    }

    void dp()
    {
        int now=0,tsf=1;
        for (int i=1;i<n[0]-'0';i++)
            f[tsf][next[root][i]][0]++;
        f[tsf][next[root][n[0]-'0']][1]++;
        for (int i=0;i<d-1;i++)
        {
            now^=1,tsf^=1;
            memset(f[tsf],0,sizeof f[tsf]);
            for (int j=1;j<W;j++)
                f[tsf][next[root][j]][0]++;
            for (int j=0;j<tot;j++)
                if (!mark[j])
                {
                    (f[tsf][next[j][n[i+1]-'0']][1]+=f[now][j][1])%=P;
                    for (int k=0;k<n[i+1]-'0';k++)
                        (f[tsf][next[j][k]][0]+=f[now][j][1])%=P;
                    for (int k=0;k<W;k++)
                        (f[tsf][next[j][k]][0]+=f[now][j][0])%=P;
                }
        }
        ans=0;
        for (int i=0;i<tot;i++)
            if (!mark[i])
                (((ans+=f[tsf][i][0])%=P)+=f[tsf][i][1])%=P;
    }
}atm;

int main()
{
    freopen("count.in","r",stdin);
    freopen("count.out","w",stdout);
    scanf("%s",n);
    d=strlen(n);
    atm.init();
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",str);
        atm.insert();
    }
    atm.build();
    atm.dp();
    printf("%d\n",ans);
    fclose(stdin);
    fclose(stdout);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值