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;}
- SMOJ 1981 豆腐(数位DP+AC自动机)
- SMOJ 1767 子串个数 (AC自动机+状压DP)
- SMOJ 2019 歌词 (AC自动机)
- BUPT 652 Confusing Problem(AC自动机+数位DP)
- ZOJ3494 BCD Code (AC自动机+数位DP)
- ZOJ - 3494 BCD Code(AC自动机+数位DP)
- ZOJ 3494 BCD Code (AC自动机 + 数位DP)
- bzoj 3530: [Sdoi2014]数数 (AC自动机+数位DP)
- [BZOJ3530][Sdoi2014]数数(AC自动机+数位dp)
- [BZOJ3530][Sdoi2014]数数(AC自动机+数位DP)
- ZOJ 3494 BCD Code(数位dp+AC自动机)
- bzoj3530 [Sdoi2014]数数(AC自动机+数位DP)
- CC FAVNUM (AC自动机+数位DP)
- 【AC自动机+数位DP】SDOI2014 数数
- HDU 4518 ac自动机+数位dp
- zoj3494 BCD Code ac自动机+数位dp
- BZOJ 3530 数数【AC自动机+数位dp】
- zoj 3494(ac自动机+数位dp)
- 解决“A Java Exception has occurred”以及“开启bat文件出现闪弹”的问题
- 容斥 贡献
- 6-1 SQL介绍
- 堆,栈,内存泄露,内存溢出介绍
- 1.数组---查找 2.数组--二分查找法 3.数组--二分查找法的应用
- SMOJ 1981 豆腐(数位DP+AC自动机)
- 【SpringMVC】架构理解
- 联合/先验/后验概率、似然函数
- MyEclipse2014 常用设置优化
- Java常用排序算法
- SetWindowsHookEx 函数,暂时没有深入研究
- hihoCoder 1584 Bounce 【数学规律】 (ACM-ICPC国际大学生程序设计竞赛北京赛区(2017)网络赛)
- 区间平均值(逆序对)
- ImportError: cannot import name 'downsample'