hdu3247 Resource Archiver

題面在這裏

題意:

給你n個資源串,m個病毒串,都由01組成。需要構造一個新串使得n個資源串都是這個新串的子串,並且該新串不包含任意一個病毒串。求新串的最小長度。
2 <= n <= 10, 1 <= m <= 1000
每個資源串長度<=1000,所有病毒串長度<=50000。

做法:

對於所有的病毒串和資源串,把它們都扔進AC自動機處理。
對於每一個節點處理出是否含病毒,以及包含了哪幾個資源串(狀壓)。
f[i][j]表示當前使用的資源串狀壓爲i,現在在j這個節點的最小長度
如果直接枚舉下一步走的節點轉移,發現會超時,時間複雜度上界大概是(50000+10*1000)*1024*2+大常數>1s。

考慮優化,發現每次轉移的時候,有很多節點不改變i這個狀壓,但我們最後需要的狀壓是2^n-1。所以一個一個節點轉移十分浪費時間。
所以我們可以採用預處理的方式。ac自動機本身就是一個圖,我們將所有資源串末尾節點取出來,每個點跑一個bfs,求出它到其他點的最短路(bfs的時候病毒串末尾不能走)。
於是每次只要轉移到這幾個資源串末尾節點就可以了,f[i][j]表示當前使用的資源串狀壓爲i,現在在j這個節點(j是資源串末尾節點的編號)。

代碼:

/*************************************************************
    Problem: hdu 3247 Resource Archiver
    User: fengyuan07
    Language: C++
    Result: Accepted
    Time: 62MS
    Memory: 3868K
    Submit_Time: 2018-01-23 20:40:22
*************************************************************/

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cctype>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;

const int N = 100010;
const int inf = 1e9;
int n, m, tot, cnt;
int c[N][2], fail[N], w[N], f[1500][60], e[60][60], pos[60], dis[N];
char str[N];

inline void insert(char s[], int x) {
    int len = strlen(s), o = 0;
    for(int i = 0; i < len; i ++) {
        int t = s[i]-'0';
        if(!c[o][t]) c[o][t] = ++ tot;
        o = c[o][t];
    }
    if(w[o] == -1) return;
    if(x == -1) w[o] = -1; else w[o] |= 1<<x-1;//-1是病毒串,否則記錄當前含有資源串的狀壓
}
inline void build() {
    queue<int> q;
    for(int i = 0; i < 2; i ++) if(c[0][i]) q.push(c[0][i]), fail[c[0][i]] = 0;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        if(w[fail[u]] == -1) w[u] = -1; else w[u] |= w[fail[u]];//把fail[u]節點的信息傳到u上來
        for(int i = 0; i < 2; i ++)
            if(c[u][i]) q.push(c[u][i]), fail[c[u][i]] = c[fail[u]][i];
            else c[u][i] = c[fail[u]][i];
    }
}
inline void bfs(int s) {
    queue<int> q; q.push(pos[s]); for(int i = 0; i <= tot; i ++) dis[i] = inf;
    dis[pos[s]] = 0;
    while(!q.empty()) {
        int u = q.front(); q.pop();
        for(int i = 0; i < 2; i ++) {
            int v = c[u][i];
            if(v <= tot && w[v] >= 0)//是存在的節點,並且不是病毒串末尾
                if(dis[v] == inf) { q.push(v); dis[v] = dis[u]+1; }
        }
    } for(int i = 1; i <= cnt; i ++) if(dis[pos[i]] < inf) e[s][i] = dis[pos[i]];
}
int main() {
    while(~scanf("%d%d", &n, &m) && (n||m)) {
        tot = 0; memset(c, 0, sizeof c); memset(fail, 0, sizeof fail); memset(w, 0, sizeof w);
        for(int i = 1; i <= n; i ++) { scanf("%s", str); insert(str, i); }
        for(int i = 1; i <= m; i ++) { scanf("%s", str); insert(str, -1); }
        build();
        cnt = 1;//以下是一些預處理
        pos[1] = 0;
        for(int i = 0; i <= tot; i ++)
            if(w[i] > 0) pos[++ cnt] = i;//記錄非病毒串且是資源串的末尾的節點
        memset(e, -1, sizeof e);//-1表示這兩個點之間不存在路徑
        for(int i = 1; i <= cnt; i ++) bfs(i);//對每個節點bfs找最短路
        //以下是dp,f[i][j]表示當前使用的資源串狀壓爲i,現在在j這個節點(資源串末尾節點的編號)
        memset(f, 0x3f, sizeof f); f[0][1] = 0;
        for(int i = 0; i < 1<<n; i ++)
            for(int j = 1; j <= cnt; j ++) if(f[i][j] < 1e9) {
                for(int k = 1; k <= cnt; k ++) {
                    if(e[j][k] < 0 || j == k) continue;//兩點之間不存在路徑就退出
                    f[i|w[pos[k]]][k] = min(f[i|w[pos[k]]][k], f[i][j]+e[j][k]);
                }
            } int ans = inf;
        for(int i = 1; i <= cnt; i ++) ans = min(ans, f[(1<<n)-1][i]);
        printf("%d\n", ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章