HDU 3247 Resource Archiver AC自动机 + BFS最短路 + 状压DP

题目大意:

现在有n个(2 <= n <= 10)个需要合并起来的文件串,然后又m个( 1 <= m <= 1000)个病毒串(病毒串的总长度不超过5000),两类串都只包含二进制的0和1,现在要求找到一个最短的串,使得这个串中包含所有的文件串并且不包含病毒串。


样例:

2 2

1110

0111

101

1001

0 0


输出为5,因为最短的串是01110,不包含病毒串


大致思路:

首先将所有的文件串和病毒串都加入到Trie树建立状态转移图,end[ i ]表示编号为i的节点时病毒结尾还是文件结尾还是不是结尾,如果是病毒结尾或者fail[i]指针指向的点的end是-1,则此点也是-1,表示不可走。

所有文件结尾的点用end数组的状压来标记是包含哪几个文件的结尾点。


那么首先从根节点和所有文件结尾点用bfs来得到根节点和文件节点(end > 0)也可能是多个文件的结尾)两两之间的距离。

那么就是一个简单的状压dp了,起始点是根节点,知道到达每一个点可以得到什么样的状态,花费是多少,就是一个常规的状压,状态转移方程见代码


代码如下:

Result  :  Accepted     Memory  :  1592 KB     Time  :  46 ms

/*
 * Author: Gatevin
 * Created Time:  2014/11/24 18:43:03
 * File Name: Kagome.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;

const int inf = 0x3f3f3f3f;
int n, m;
char ts[50010];
/*
 * 所有病毒(共1000个的总长度不超过50000按道理来说一个病毒串可以长达49000
 * 但是看kuangbin的代码只开了1010来读病毒串也过了...
 * 数据弱了么..
 */

struct Trie
{
    int next[60010][2], fail[60010], end[60010];
    int L, root;
    int dis[60010];
    int dp[1025][11];//11已经够了,第二位表示end为正的数量不会超过n(从Trie树的性质上不难发现),虽然的确纠结了一下
    int mat[11][11];//表示需要到达的点之间的距离,值为-1时时不可到达
    int pos[11];
    int cnt;
    int newnode()
    {
        for(int i = 0; i < 2; i++)
            next[L][i] = -1;
        end[L++] = 0;
        return L - 1;
    }
    void init()
    {
        L = 0;
        root = newnode();
        return;
    }
    void insert(char *s, int id)
    {
        int now = root;
        for(; *s; s++)
        {
            if(next[now][*s - '0'] == -1)
                next[now][*s - '0'] = newnode();
            now = next[now][*s - '0'];
        }
        if(id == -1 || end[now] == -1) end[now] = -1;
        else end[now] |= (1 << id);
    }
    void build()
    {
        queue <int> Q;
        Q.push(root);
        fail[root] = root;
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            if(end[fail[now]] < 0) end[now] = -1;
            else end[now] |= end[fail[now]];//end数组已经反应了不同文件之间的包含关系
            for(int i = 0; i < 2; 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;
    }
    void bfs(int id)
    {
        queue <int> Q;
        Q.push(pos[id]);
        memset(dis, -1, sizeof(dis));
        dis[pos[id]] = 0;
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            /*
            int tmp = fail[now];
            while(tmp != root)
            {
                dis[tmp] = dis[now];
                Q.push(tmp);
                tmp = fail[tmp];
            }
            这一段没有必要,因为不需要记录fail指针的节点的关系距离为零,这一信息在end数组中已经反映出来了
            加上这一段无用功反而会超时
            */
            for(int i = 0; i < 2; i++)
            {
                if(end[next[now][i]] >= 0 && dis[next[now][i]] < 0)
                {
                    dis[next[now][i]] = dis[now] + 1;
                    Q.push(next[now][i]);
                }
            }
        }
        for(int i = 0; i < cnt; i++)
            mat[id][i] = dis[pos[i]];
        return;
    }
    void solve()
    {
        pos[0] = 0;//根节点为起点,也需要加入计算最短距离
        cnt = 1;
        for(int i = 0; i < L; i++)
            if(end[i] > 0) pos[cnt++] = i;
        for(int i = 0; i < cnt; i++)
            bfs(i);
        for(int i = 0; i < (1 << n); i++)
            for(int j = 0; j < cnt; j++)
                dp[i][j] = inf;
        dp[0][0] = 0;
        for(int i = 0; i < (1 << n); i++)
            for(int j = 0; j < cnt; j++)
                if(dp[i][j] < inf)
                {
                    for(int k = 0; k < cnt; k++)
                    {
                        if(mat[j][k] < 0) continue;//不可从j到达k
                        dp[i | end[pos[k]]][k] = min(dp[i | end[pos[k]]][k], dp[i][j] + mat[j][k]);
                        //常见的状态压缩dp
                    }
                }
        int ans = inf;
        for(int i = 0; i < cnt; i++)
            ans = min(ans, dp[(1 << n) - 1][i]);
        printf("%d\n", ans);
        return;
    }
};

Trie AC;

int main()
{
    while(scanf("%d %d", &n, &m), n | m)
    {
        AC.init();
        for(int i = 0; i < n; i++)
        {
            scanf("%s", ts);
            AC.insert(ts, i);
        }
        for(int i = 0; i < m; i++)
        {
            scanf("%s", ts);
            AC.insert(ts, -1);
        }
        AC.build();
        AC.solve();
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值