UVALive 3907 (LA 3907) Puzzle AC自动机 + 搜索DP 记录路径

题目大意:

给定s个字符串(1 <= s <= 1000),  每个字符串的长度不超过50, 给定n表示接下来只能使用字母表的前n个字符, 现在要求构造字符串使得构造的字符串中不能出现这s个串, 如果这样的串不存在或长度为无限, 输出No, 否则输出最长的这样的串


大致思路:

首先利用AC自动机插入这s个串建立状态转移图, 然后再得到的有向图中搜索, 从根节点开始, 如果存在不经过禁止点的环, 那么输出No, 否则这个图不考虑禁止点是一个DAG, 选取跟节点AC自动机起点之后找最长路径, dfs即可, 可以用记忆化搜索加快对最长长度的搜索, 每次从当前点向之后转移的时候, 优先选字典序最大的字符边保证结果长度最大的条件时选择的路径得到的字符串字典序最大

如果最大长度为0输出No, 否则从根节点开始按照记录的每个节点之后对应的选择字符跟踪出字符串

我写dfs的时候刚开始犯了一个错误, 犯的错误也记录在代码注释中了

细节看代码吧


代码如下:

Result  :  Accepted     Memory  :  ? KB     Time  :  0 ms

/*
 * Author: Gatevin
 * Created Time:  2015/2/15 13:23:28
 * File Name: Mononobe_Mitsuki.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

int t, n, s;

struct Trie
{
    int next[50010][26], fail[50010];
    bool end[50010];
    int L, root;
    bool loop, vis[50010];
    int dp[50010], road[50010];
    int newnode()
    {
        for(int i = 0; i < n; i++)
            next[L][i] = -1;
        end[L++] = 0;
        return L - 1;
    }
    void init()
    {
        L = 0;
        root = newnode();
        return;
    }
    void insert(char *s)
    {
        int now = root;
        for(; *s; s++)
        {
            if(next[now][*s - 'A'] == -1)
                next[now][*s - 'A'] = newnode();
            now = next[now][*s - 'A'];
        }
        end[now] = 1;
        return;
    }
    void build()
    {
        fail[root] = root;
        queue <int> Q;
        Q.push(root);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            end[now] |= end[fail[now]];//标记所有不能走到的点
            for(int i = 0; i < n; i++)
                if(next[now][i] == -1)
                    next[now][i] = now == root ? root : next[fail[now]][i];
                else
                {
                    fail[next[now][i]] = now == root ? root : next[fail[now]][i];
                    Q.push(next[now][i]);
                }
        }
        return;
    }
    /*
     * dp[i]是为了记忆化dp, 表示当前走到了i点接下来能走下去的最长长度
     * 我刚开始写的是dfs(now, len), 然后每次记录road[len] = i来记录路径, 但是这会存在一个错误
     * 就是比如说之前记录的是road[0~r], 当搜索下一次到road[i]之后因为记忆化搜索提前终止
     * 会导致后半段的road[i]没来得及更新, 于是造成长度正确但是路径记录错误的情况
     * 之后找到了好的替代方法
     */
    int dfs(int now)//之前写的写法是dfs(int now, int len) len表示当前走了的路径长度
    {
        if(loop) return 0;
        if(dp[now] != -1) return dp[now];
        dp[now] = 0;
        for(int i = n - 1; i >= 0; i--)
        {
            if(!end[next[now][i]])
            {
                if(vis[next[now][i]])//走到走过的位置, 发现回路
                {
                    loop = true;
                    return 0;
                }
                else
                {
                    vis[next[now][i]] = 1;
                    if(dp[now] < 1 + dfs(next[now][i]))//之前dfs(next[now][i], len + 1)
                    {
                        dp[now] = 1 + dp[next[now][i]];
                        road[now] = i;//在点now处选择为i
                        /* 
                         * 之前写的road[len] = i, 犯了记忆化导致更新不完全的禁忌
                         * 现在改变成记录在每个顶点的位置的最佳选择之后, 就算记忆化跳过部分搜索
                         * 也可以跟踪每个点的最佳选择找到目标串
                         */
                    }
                    vis[next[now][i]] = 0;
                }
            }
        }
        return dp[now];
    }
    void solve()
    {
        loop = false;
        memset(vis, 0, sizeof(vis));
        memset(dp, -1, sizeof(dp));
        vis[root] = 1;
        dfs(root);
        if(!dp[0] || loop)
            printf("No\n");
        else
        {
            int now = 0;
            for(int i = 0; i < dp[0]; i++)
            {
                printf("%c", road[now] + 'A');
                now = next[now][road[now]];//类似链表, 依次跟踪当前点之后的最佳路线
            }
            printf("\n");
        }
        return;
    }
};

Trie AC;
char in[60];

int main()
{
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d %d", &n, &s);
        AC.init();
        while(s--)
        {
            scanf("%s", in);
            AC.insert(in);
        }
        AC.build();
        AC.solve();
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值