【NOIP2017提高A组模拟8.23】密码

Description:

这里写图片描述

题解:

这肯定是数位dp,十分显然。
首先对n个串建一个AC自动机,这样我们就可以表示出当前dp的状态了。
fi,j,k,0/1 表示从高往低已经确定了前i个位,匹配数为j,包含当前第i位的后缀走到了AC自动机的第j位,是否顶满。
转移就相当于在AC自动机通过fail跳,预处理一下就行了。
可以维护一个数组表示在AC自动机上的第i个点,这个点代表的字符串的所有后缀包含多少个密匙,需要注意更新顺序需要和fail指针一样。

Code:

#include<cstdio>
#include<cstring>
#define ll long long
#define fo(i, x, y) for(ll i = x; i <= y; i ++)
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const ll mo = 1e9 + 7;
ll n, k, len, a[505], m, ans;
char s[505], s2[505], s3[505];
ll root = 1, tot = 1, next[1000][10], fail[1000], sum[1000], up[1000]; 
ll d[1005];
ll f[1005][12][500][2];
ll zf[500][12];

void Init() {
    scanf("%lld %lld", &n, &k);
    scanf("%s", s + 1); scanf("%s", s2 + 1);
    fo(i, 1, n) {
        scanf("%s", s3 + 1);
        len = strlen(s3 + 1);
        ll k = root;
        fo(j, 1, len) {
            if(!next[k][s3[j] - 48]) next[k][s3[j] - 48] = ++ tot;
            k = next[k][s3[j] - 48];
        }
        sum[k] ++;
    }
}

void Build_ac() {
    fail[root] = 0;
    d[1] = root;
    for(ll st = 1, en = 1; st <= en; st ++) {
        ll x = d[st];
        fo(i, 0, 9) if(next[x][i]) {
            ll y = next[x][i], j = fail[x];
            while(j != 0 && !next[j][i])
                j = fail[j];
            fail[y] = next[j][i]; if(fail[y] == 0) fail[y] = root;
            d[++ en] = y;
        }
    }
    fo(i, 1, tot) {
        ll j = i;
        while(j != root) {
            up[i] += sum[j]; j = fail[j];
        }
    }
}

void Build() {
    fo(j, 1, tot) {
        fo(num, 0, 9) {
            ll g = j;
            while(g != root && !next[g][num])
                g = fail[g];
            if(next[g][num]) g = next[g][num];  
            zf[j][num] = g;
        }
    }
}
void Do() {
    memset(f, 0, sizeof(f));
    f[0][0][root][1] = 1;
    fo(i, 1, m) {
        fo(j, 1, tot) {
            fo(num, 0, 9) {
                ll p = zf[j][num];
                fo(u, 0, k) f[i][min(k, u + up[p])][p][0] += f[i - 1][u][j][0];
            }
        }
        fo(j, 1, tot) {
            fo(num, 0, a[i] - 1) {
                ll p = zf[j][num];
                fo(u, 0, k) f[i][min(k, u + up[p])][p][0] += f[i - 1][u][j][1];
            }
        }
        fo(j, 1, tot) {
            ll num = a[i];
            ll p = zf[j][num];
            fo(u, 0, k) f[i][min(k, u + up[p])][p][1] += f[i - 1][u][j][1];
        }
        fo(j, 1, tot) fo(u, 0, k) f[i][u][j][0] %= mo, f[i][u][j][1] %= mo;
    }
    fo(j, 1, tot)
        ans += f[m][k][j][0] + f[m][k][j][1], ans %= mo;
}

int main() {
    freopen("word.in", "r",  stdin);
    freopen("word.out", "w",  stdout);
    Init();
    Build_ac();
    Build();
    m = strlen(s + 1);
    fo(i, 1, m) a[i] = s[i] - 48;
    Do();
    ans = -ans;
    m = strlen(s2 + 1);
    fo(i, 1, m) a[i] = s2[i] - 48;
    Do();
    ans = (ans % mo + mo) % mo;
    printf("%lld\n", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值