SMOJ 1981 豆腐(数位DP+AC自动机)

来源:互联网 发布:淘宝店铺发微淘 编辑:程序博客网 时间:2024/04/28 20:04

Description

这里写图片描述
这里写图片描述
这里写图片描述


Solution

这题看上去是一道很典型的数位DP。由于又涉及字符串的匹配,所以我们同时考虑AC自动机

我们按照数位DP的方法,算出[1, R]的答案,然后减去[1, L-1]的答案再相减。由于我太久没写过数位DP了,省赛前也没有好好学,所以对数位DP我仍然一头雾水。在膜了kekxy的博客后,我终于会做这题了。

数位DP需要记的东西,包括但不限于以下几个:当前做到数字的第几位,当前的状态(视题目而定),是否有前导零(可能不用记),是否已经达到了上界(即满位),dp数组的答案(方案数)。

在这题中,尽管进制不一定是十进制,但是做法也是一样的。这里要用到AC自动机,我发现写成记忆化搜索的形式比较麻烦,于是就改成了递推的写法。与记忆化不同的是,递推前导零和满位这两个状态也要保存在数组里。

这题求麻辣值小于k的豆腐数量,而k比较小,于是将k记作一维状态。我们需要知道当前的字符串(m进制数)有多少个数字串匹配,就对数字串建AC自动机,由于每个串匹配其后缀也必然匹配,于是就要在当前节点记录其fail指针一路向上的贡献值之和。而向下亦可以走到其任何一个fail指针的儿子,于是我们构建trie图,将节点没有的儿子指向其fail指针的儿子(fail指针是Root就指向Root)。这样fail指针的更新也就可以直接用父亲的fail指针的儿子。相当于本来要走上斜坡,现在已经将斜坡上有用的信息提前滚到坡底。然后我们就记当前在哪个节点,在自动机上向下走(枚举数字)同时更新各个状态,累计方案数即可。

注意构建trie图和构建AC自动机、fail树是略有区别的。

而本题数字串有前导零,所以为了避免多匹配,前导零是要记的,转移时如果遇到前导零就要回到Root(不放到Root的话就会少转移),当前做到哪个和是否满位是数位DP必须记的。

清楚地列出最终DP的状态:

F[第i位][字符串的状态][当前的麻辣值][是否有前导零][是否达到上界]=方案数

这样的时间复杂度理论上是超了5s的,但是依旧跑得比较快。

最后我想讲一下我做此题时的心情。比赛时写了个AC自动机的暴力,细节打错样例都没过。国庆好不容易有时间改题了,结果改得我“废寝忘食”,改了lz一个下午,连晚饭都忘了吃,一直70分,又拍又调,最后竟然又是忘了考虑相同的字符串??!!mmp!以后我一定会像记国歌一样将AC自动机一定要考虑相同字符串,权值什么的记得累加牢记于心。

最后只能说:我的数位DP太菜了,一定要增强考虑算法细节和题目特例的意识。


Code

#include <iostream>#include <cstdio>#include <algorithm>#include <cstdlib>#include <cmath>#include <cstring>#define maxl 205#define maxk 505#define MOD 1000000007using namespace std;int n, m, K, cnt;int L[maxl], R[maxl], num[maxl];int f[maxl][maxl][maxk][2][2];struct AC{    AC *son[20], *fail;    int val, id;    void Clear(){        for(int i = 0; i < 20; i++)  son[i] = NULL;        val = 0;        id = cnt;    }}Node[maxl], *q[maxl], *Root;AC *NewTnode(){    Node[cnt].Clear();    return Node+cnt++;}void AC_Insert(int *x, int v){    AC *now = Root;    for(int i = 1; i <= x[0]; i++){        if(!now->son[x[i]])  now->son[x[i]] = NewTnode();        now = now->son[x[i]];    }    now->val += v;}void AC_Buildfail(){    Root->fail = NULL;    q[0] = Root;    int head = 0, tail = 0;    while(head <= tail){        AC *now = q[head++];        for(int i = 0; i < m; i++){            if(now->son[i]){                q[++tail] = now->son[i];                now->son[i]->fail = (now == Root) ? Root : now->fail->son[i];                now->son[i]->val += now->son[i]->fail->val;            }            else  now->son[i] = (now == Root) ? Root : now->fail->son[i];        }    }}int Sol(int *A){    memset(f, 0, sizeof(f));    f[0][0][0][1][1] = 1;    for(int i = 0; i < A[0]; i++)        for(int j = 0; j < cnt; j++)            for(int k = 0; k <= K; k++)                for(int lead = 0; lead < 2; lead++)                    for(int lim = 0; lim < 2; lim++){                        if(!f[i][j][k][lead][lim])  continue;                        int up = lim ? A[i+1] : m-1;                        for(int d = 0; d <= up; d++){                            int nj = (lead && !d) ? 0 : Node[j].son[d]->id, nk = k + Node[nj].val;                            if(nk > K)  continue;                            int nlead = lead && !d,  nlim = lim && (d==A[i+1]);                            f[i+1][nj][nk][nlead][nlim] = (f[i+1][nj][nk][nlead][nlim] + f[i][j][k][lead][lim]) % MOD;                        }                    }    int ans = 0;    for(int j = 0; j < cnt; j++)        for(int k = 0; k <= K; k++)            for(int lead = 0; lead < 2; lead++)                for(int lim = 0; lim < 2; lim++)                    ans = (ans + f[A[0]][j][k][lead][lim]) % MOD;    return ans;}int main(){    freopen("1981.in", "r", stdin);    freopen("1981.out", "w", stdout);    scanf("%d%d%d", &n, &m, &K);    scanf("%d", &L[0]);    for(int i = 1; i <= L[0]; i++)  scanf("%d", &L[i]);    scanf("%d", &R[0]);    for(int i = 1; i <= R[0]; i++)  scanf("%d", &R[i]);    L[L[0]] --;     for(int i = L[0]; L[i] < 0; i--){        L[i] += m;        L[i-1] --;    }    Root = NewTnode();    int v;    for(int i = 1; i <= n; i++){        scanf("%d", &num[0]);        for(int j = 1; j <= num[0]; j++)  scanf("%d", &num[j]);        scanf("%d", &v);        AC_Insert(num, v);    }    AC_Buildfail();    printf("%d", (Sol(R) - Sol(L) + MOD) % MOD);    return 0;}

这里写图片描述

原创粉丝点击