hdu 3247(ac自动机+状态压缩dp+最短路)

题意:有n个源文件也就是n个字符串(n<=10 长度<=1000),m种病毒也同样是m个字符串(m<=1000,总长度50000),现在要问最短的串包含所有源文件但不存在一个子串是病毒的长度,源文件的串可以重叠存在,所有字符串由01组成。
题解:好题,思路来自这里http://m.blog.youkuaiyun.com/blog/woshi250hua/8021283
问题可以转化为找出一个最短的串,包含所有源文件字符串且每个字符串可重叠出现仅一次,但不包含任何一个病毒字符串。因为源文件字符串最多10个,可以用状态压缩,每个位0表示没有出现,1表示出现。先把两种模式串都存到trie图中,val[i]表示存节点i的对应源文件状态,如果是病毒存成-1。f[s][i]是状态是s添加第i个源文件字符串的情况下最短串长度,状态转移f[s | val[flag[k]]][k] = min(f[s | val[flag[k]]][k], f[s][j] + dis[j][k])。这里flag[k]存第k个源文件节点编号,dis[j][k]是从第j个源文件跳转到第k个源文件的最小步数,用求最短路的方式求得。为什么是这个跳转的最小步数,是因为在trie图中,假如当前节点是i,它的fail指针指向的另一个节点j,那么从根节点到节点j的这个前缀串一定是根节点到节点i的一个后缀串,且一定是最长后缀,所以dis[j][k]这条路径是从根节点开始,先走到节点flag[j],完成了源文件flag[j]串,最后通过fail指针走到节点flag[k],整条路径刚好是包含了两个重叠的源文件串,自己画图能更好理解。所以预处理出所有节点到其他节点的”最短路”就能优化求最短长度符合要求的串。最后在f[(1 << n) - 1][i]中找最小值就是解。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 60005;
const int INF = 0x3f3f3f3f;
int Next[N][2], val[N], fail[N], sz, n, m;
int flag[11], f[1030][11], cnt, d[N], vis[N], mindis[11][11];
char str[N];

void init() {
    memset(Next[0], 0, sizeof(Next[0]));
    val[0] = 0;
    sz = 1;
}

void insert(char *s, int v) {
    int u = 0, len = strlen(s);
    for (int i = 0; i < len; i++) {
        int k = s[i] - '0';
        if (!Next[u][k]) {
            memset(Next[sz], 0, sizeof(Next[sz]));
            val[sz] = 0;
            Next[u][k] = sz++;
        }
        u = Next[u][k];
    }
    val[u] = v;
}

void getFail() {
    queue<int> Q;
    fail[0] = 0;
    for (int i = 0; i < 2; i++)
        if (Next[0][i]) {
            fail[Next[0][i]] = 0;
            Q.push(Next[0][i]);
        }
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        if (val[fail[u]] == -1)
            val[u] = -1;
        else val[u] |= val[fail[u]];
        for (int i = 0; i < 2; i++) {
            if (!Next[u][i])
                Next[u][i] = Next[fail[u]][i];
            else {
                fail[Next[u][i]] = Next[fail[u]][i];
                Q.push(Next[u][i]);
            }
        }
    }
}

void spfa(int s) {
    queue<int> Q;
    memset(d, INF, sizeof(d));
    memset(vis, 0, sizeof(vis));
    d[s] = 0;
    vis[s] = 1;
    Q.push(s);
    while (!Q.empty()) {
        int u = Q.front();
        Q.pop();
        vis[u] = 0;
        for (int i = 0; i < 2; i++) {
            if (d[Next[u][i]] > d[u] + 1 && val[Next[u][i]] >= 0) {
                d[Next[u][i]] = d[u] + 1;
                if (!vis[Next[u][i]]) {
                    Q.push(Next[u][i]);
                    vis[Next[u][i]] = 1;
                }
            }
        }
    }
}

int solve() {
    flag[0] = 0;
    cnt = 1;
    for (int i = 0; i < sz; i++)
        if (val[i] > 0)
            flag[cnt++] = i;
    for (int i = 0; i < cnt; i++) {
        spfa(flag[i]);
        for (int j = 0; j < cnt; j++)
            mindis[i][j] = d[flag[j]];
    }
    int all = 1 << n;
    memset(f, INF, sizeof(f));
    f[0][0] = 0;
    for (int i = 0; i < all; i++)
        for (int j = 0; j < cnt; j++)
            if (f[i][j] < INF) {
                for (int k = 0; k < cnt; k++) {
                    if (mindis[j][k] == INF || j == k)
                        continue;
                    f[i | val[flag[k]]][k] = min(f[i | val[flag[k]]][k], f[i][j] + mindis[j][k]);
                }
            }
    int res = INF;
    for (int i = 0; i < cnt; i++)
        res = min(res, f[all - 1][i]);
    return res;
}

int main() {
    while (scanf("%d%d", &n, &m) == 2 && n + m) {
        init();
        for (int i = 0; i < n; i++) {
            scanf("%s", str);
            insert(str, 1 << i);
        }
        for (int i = 0; i < m; i++) {
            scanf("%s", str);
            insert(str, -1);
        }
        getFail();
        printf("%d\n", solve());
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值