[BZOJ 3530] [Sdoi2014] 数数 【AC自动机+DP】

题目链接:BZOJ - 3530

题目分析

明显是 AC自动机+DP,外加数位统计。

WZY 神犇出的良心省选题,然而去年我太弱..比现在还要弱得多..

其实现在做这道题,我自己也没想出完整解法..

就想出了个 O(l^3) 的做法:

完全按照数位统计的思想来,先统计长度不足 len 的数字的合法种类数,这个枚举开头,然后 AC 自动机 DP 一下,用 f[i][j] 表示到了第 i 位,在第 j 个节点上的合法数字个数。这样是 O(L^2)。

然后长度等于 n 的部分,就按照数位统计,一位位向后推,然后每次 O(L^2) DP 求,这样总的复杂度是 O(L^3)。

注意不能走包含模式串的节点,首先 Trie 中模式串的节点肯定不能走,其次,如果沿着一个节点的 Fail 链一直向上走,只要链上有一个节点是不能走的,那么这个节点也就不能走了。

所以需要从每个的节点开始,逆着 Fail 的边 DFS 一下,如果当前状态下将经过的点全都 Ban 掉。但是数据好像比较弱,不加这个也是不会出问题的。

但是这样会弄错一种情况:比如有 123, 2 这两个串,然后它就沿着 -> 1 -> 2 一直向后匹配,没有匹配成功 123 ,但是其实已经包含了 2 这个串。

这样的复杂度只有70分。

正解的复杂度是 O(L^2) 的:对于长度不足 len 的数字,还是直接求,O(L^2)。

对于长度等于 len 的数字,我们就直接用一个 O(L^2) 的 DP 求出来,我们把状态增加一维, g[i][j][k] ,前两位是第 i 位,走到第 j 个节点,k 是一个 [0/1] 变量,表示这个状态是否是沿着 n 的每一位走过来。

这样我们就知道了,如果当前状态是依照 n 的每一位走过来,那么下一步就只能转移 [0, n[i+1]] 这些数字。否则可以转移所有的数字。这样就求出了小于等于 n 的所有合法数字。

总的复杂度是 O(L^2) 。

AC自动机+DP的一般做法:f[i][j] 表示走了 i 步,走到了第 j 个节点,有时还需要加第 3 维或更多。

然后 DP 的时候就是 枚举 i ,枚举 j,枚举转移到下一步的字符。

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <queue>

using namespace std;

const int MaxL = 1500 + 5, MaxNode = 15000 + 5, Mod = 1000000007;

int len, l, m, Index, Ans;
int f[MaxL][MaxNode], g[MaxL][MaxNode][2];

char S[MaxL], Ns[MaxL];

struct Trie
{
	int x, Idx;
	bool Ban;
	Trie *Fail, *Child[10];
} TA[MaxNode], *P = TA, *Root, *Zero;

Trie* NewNode(int Num = 0)
{
	++P;
	P -> x = Num;
	P -> Idx = ++Index;
	P -> Fail = NULL;
	P -> Ban = false;
	memset(P -> Child, 0, sizeof(P -> Child));
	return P;
}

void Insert(char *S, int l)
{
	int t;
	Trie *Now = Root;
	for (int i = 1; i <= l; ++i)
	{
		t = S[i] - ‘0‘;
		if (Now -> Child[t] == NULL)
			Now -> Child[t] = NewNode(t);
		Now = Now -> Child[t];
	}
	Now -> Ban = true;
}

struct Edge
{
	int v;
	Edge *Next;
} E[MaxNode], *PE = E, *Point[MaxNode];

inline void AddEdge(int x, int y)
{
	++PE; PE -> v = y;
	PE -> Next = Point[x]; Point[x] = PE;
}

bool Visit[MaxNode];

void DFS(int x, bool f)
{
	Visit[x] = true;
	TA[x].Ban = (TA[x].Ban) || f;
	for (Edge *j = Point[x]; j; j = j -> Next)
		DFS(j -> v, TA[x].Ban);
}

queue<Trie *> Q;

void Build_Fail()
{
	while (!Q.empty()) Q.pop();
	Q.push(Root);
	Trie *Now;
	while (!Q.empty())
	{
		Now = Q.front(); Q.pop();
		AddEdge(Now -> Fail -> Idx, Now -> Idx);
		for (int i = 0; i <= 9; ++i)
		{
			if (Now -> Child[i] == NULL) Now -> Child[i] = Now -> Fail -> Child[i];
			else
			{
				Now -> Child[i] -> Fail = Now -> Fail -> Child[i];
				Q.push(Now -> Child[i]);
			}
		}
	}
	for (int i = 1; i <= Index; ++i)
		if (!Visit[i]) DFS(i, TA[i].Ban);
}

void Usual_Disco()
{
	memset(f, 0, sizeof(f));
	for (int i = 1; i <= 9; ++i)
		if (Root -> Child[i] -> Ban == false)
			++f[1][Root -> Child[i] -> Idx];
	for (int i = 1; i < len; ++i)
		for (int j = 1; j <= Index; ++j)
		{
			if (TA[j].Ban) continue;
			Ans = (Ans + f[i][j]) % Mod;
			for (int t = 0; t <= 9; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					f[i + 1][TA[j].Child[t] -> Idx] += f[i][j];
					f[i + 1][TA[j].Child[t] -> Idx] %= Mod;
				}
		}
	memset(g, 0, sizeof(g));
	for (int i = 1; i < Ns[1] - ‘0‘; ++i)
		if (Root -> Child[i] -> Ban == false)
			++g[1][Root -> Child[i] -> Idx][0];
	if (Root -> Child[Ns[1] - ‘0‘] -> Ban == false)
		++g[1][Root -> Child[Ns[1] - ‘0‘] -> Idx][1];
	for (int i = 1; i <= len; ++i)
		for (int j = 1; j <= Index; ++j)
		{
			if (TA[j].Ban) continue;
			if (i == len)
			{
				Ans = (Ans + g[i][j][0]) % Mod;
				Ans = (Ans + g[i][j][1]) % Mod;
				continue;
			}
			for (int t = 0; t <= 9; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					g[i + 1][TA[j].Child[t] -> Idx][0] += g[i][j][0];
					g[i + 1][TA[j].Child[t] -> Idx][0] %= Mod;
				}
			for (int t = 0; t < Ns[i + 1] - ‘0‘; ++t)
				if (!TA[j].Child[t] -> Ban)
				{
					g[i + 1][TA[j].Child[t] -> Idx][0] += g[i][j][1];
					g[i + 1][TA[j].Child[t] -> Idx][0] %= Mod;
				}
			if (!TA[j].Child[Ns[i + 1] - ‘0‘] -> Ban)
			{
				g[i + 1][TA[j].Child[Ns[i + 1] - ‘0‘] -> Idx][1] += g[i][j][1];
				g[i + 1][TA[j].Child[Ns[i + 1] - ‘0‘] -> Idx][1] %= Mod;
			}
		}
}

int main()
{
	scanf("%s", Ns + 1);
	len = strlen(Ns + 1);
	scanf("%d", &m);
	Root = NewNode();
	Zero = NewNode();
	Root -> Fail = Zero;
	for (int i = 0; i <= 9; ++i) Zero -> Child[i] = Root;
	for (int i = 1; i <= m; ++i)
	{
		scanf("%s", S + 1);
		l = strlen(S + 1);
		Insert(S, l);
	}
	Build_Fail();
	Usual_Disco();
	Ans %= Mod;
	cout << Ans << endl;
	return 0;
}

  

时间: 2024-08-18 06:05:19

[BZOJ 3530] [Sdoi2014] 数数 【AC自动机+DP】的相关文章

BZOJ 1030: [JSOI2007]文本生成器( AC自动机 + dp )

之前一直没调出来T^T...早上刷牙时无意中就想出错在哪里了... 对全部单词建AC自动机, 然后在自动机上跑dp, dp(i, j)表示匹配到了第i个字符, 在自动机上的j结点的方案数, 然后枚举A~Z进行转移. -------------------------------------------------------------------------- #include<bits/stdc++.h> using namespace std; #define idx(c) ((c) -

BZOJ 1212: [HNOI2004]L语言 [AC自动机 DP]

1212: [HNOI2004]L语言 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1367  Solved: 598[Submit][Status][Discuss] Description 标点符号的出现晚于文字的出现,所以以前的语言都是没有标点的.现在你要处理的就是一段没有标点的文章. 一段文章T是由若干小写字母构成.一个单词W也是由若干小写字母构成.一个字典D是若干个单词的集合. 我们称一段文章T在某个字典D下是可以被理解的,是指如果文

bzoj 1030 [JSOI2007]文本生成器(AC自动机+DP)

1030: [JSOI2007]文本生成器 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 3059  Solved: 1255[Submit][Status][Discuss] Description JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版.该软件可以随机生成一些文章―――总是生成一篇长度固定且完全随机的文章—— 也就是说,生成的文章中每个字节都是完

HDU3341 Lost&#39;s revenge(AC自动机+DP)

题目是给一个DNA重新排列使其包含最多的数论基因. 考虑到内存大概就只能这么表示状态: dp[i][A][C][G][T],表示包含各碱基个数为ACGT且当前后缀状态为自动机第i的结点的字符串最多的数论基因数 其中ACGT可以hash成一个整数(a*C*G*T+c*G*T+g*T+T),这样用二维数组就行了,而第二维最多也就11*11*11*11个. 接下来转移依然是我为人人型,我是丢进一个队列,用队列来更新状态的值. 这题果然挺卡常数的,只好手写队列,最后4500msAC,还是差点超时,代码也

hdu 2457 AC自动机+dp

DNA repair Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2004    Accepted Submission(s): 1085 Problem Description Biologists finally invent techniques of repairing DNA that contains segments c

POJ1625 Censored!(AC自动机+DP)

题目问长度m不包含一些不文明单词的字符串有多少个. 依然是水水的AC自动机+DP..做完后发现居然和POJ2778是一道题,回过头来看都水水的... dp[i][j]表示长度i(在自动机转移i步)且后缀状态为自动机第j个结点的合法字符串数 dp[0][0]=1 转移转移... 注意要用高精度,因为答案最多5050. 还有就是要用unsigned char,题目的输入居然有拓展的ASCII码,编码128-255. 1 #include<cstdio> 2 #include<cstring&

HDU2457 DNA repair(AC自动机+DP)

题目一串DNA最少需要修改几个基因使其不包含一些致病DNA片段. 这道题应该是AC自动机+DP的入门题了,有POJ2778基础不难写出来. dp[i][j]表示原DNA前i位(在AC自动机上转移i步)且后缀状态为AC自动机结点j的最少需要修改的基因数 转移我为人人型,从dp[i][j]向ATCG四个方向转移到dp[i+1][j'],如果结点被标记包含致病基因就不能转移. 1 #include<cstdio> 2 #include<cstring> 3 #include<que

tyvj P1519 博彩游戏(AC自动机+DP滚动数组)

P1519 博彩游戏 背景 Bob最近迷上了一个博彩游戏…… 描述 这个游戏的规则是这样的:每花一块钱可以得到一个随机数R,花上N块钱就可以得到一个随机序列:有M个序列,如果某个序列是产生的随机序列的子串,那么就中奖了,否则不中.Bob会告诉你这M个序列,和身上有的钱的总数N,当然还有R的范围.请你告诉Bob中奖的概率有多少? 输入格式 第一行三个用空格隔开的数N.M和R的范围R.其中1<=R<=9,0<N<=60,0<M<=20000.下面M行每行一个字符串(长度小于

POJ1625---Censored!(AC自动机+dp+高精度)

Description The alphabet of Freeland consists of exactly N letters. Each sentence of Freeland language (also known as Freish) consists of exactly M letters without word breaks. So, there exist exactly N^M different Freish sentences. But after recent