zoukankan      html  css  js  c++  java
  • [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;
    }
    

      

  • 相关阅读:
    2018上C语言程序设计(高级)作业-第0次作业
    最后一次作业-- 总结报告
    第14、15教学周作业
    第七周作业
    第六周作业
    第四周作业
    C语言--第四次作业
    C语言--第三次作业
    C-语言第二次作业(大一下)
    TRY
  • 原文地址:https://www.cnblogs.com/JoeFan/p/4457460.html
Copyright © 2011-2022 走看看