題意:
給你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;
}