这道题也很有意思,综合了ac自动机,dp与最短路。本题要求构造出包含所有Resource串,且不含任何的病毒串。不含任何病毒串可以转换成不能走到字典树上某个节点,所以很自然的可以想到本题的关键是从一个resource串转移到另一个需要的最短的新增长度。结合字典树,从一个串转移到另一个串,是不是就可以理解成从前一个串的最后一个字符指向的节点走到后一个串的最后一个字符所指向的节点?这样就在某种程度上转化成了tsp问题。我们先用最短路算法求出所有resource串转移到另一个resource串需要的步骤,就是在字典树上走了几步,然后就可以dp了。由于n小于10,所以我们还是可以用之前提到过的状压,将表示10个字符串是否出现的状态放到一维。
注意对resource串去重。
#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <queue>
#include <map>
#include <cstring>
#define fi first
#define se second
#define FIN freopen("in.txt","r",stdin)
#define FIO freopen("out.txt","w",stdout)
#define INF 0x3f3f3f3f
#define per(i,a,n) for(int i = a;i < n;i++)
#define rep(i,a,n) for(int i = n;i > a;i--)
#define pern(i,a,n) for(int i = a;i <= n;i++)
#define repn(i,a,n) for(int i = n;i >= a;i--)
#define fastio std::ios::sync_with_stdio(false)
#define all(a) a.begin(), a.end()
#define ll long long
#define pb push_back
#define endl "\n"
#define pii pair<int,int>
#define sc(n) scanf("%d", &n)
#define CASET int ___T; scanf("%d", &___T); for(int cs=1;cs<=___T;cs++)
template<typename T> inline void _max(T &a,const T b){
if(a<b) a = b;}
template<typename T> inline void _min(T &a,const T b){
if(a>b) a = b;}
using namespace std;
//inline ll read(){
// ll a=0;int f=0;char p=getchar();
// while(!isdigit(p)){f|=p=='-';p=getchar();}
// while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=getchar();}
// return f?-a:a;
//}
const int maxn = 300000;
const int maxnode = 2;
int ch[maxn][maxnode]; //字典树
int cnt[maxn]; //单词出现次数
int id[20];
int sz;
int fail[maxn];
int flag[maxn];
char s[1200];
int n,m;
int dp[20][2000];
int dist[100][100];
int temp[maxn];
int in[maxn];
int spfaqu[maxn];
void init()
{
sz = 1;
memset(id,0,sizeof(id));
memset(flag,0,sizeof(flag));
memset(ch[0], 0, sizeof(ch[0]));
memset(cnt,0,sizeof(cnt));
memset(dp,INF,sizeof(dp));
cnt[0] = 0;
}
void insert(char str[], int len,int f) //插入字符串
{
int u = 0;
per(i, 0, len)
{
int v = str[i]-'0';
if (!ch[u][v])
{
memset(ch[sz], 0, sizeof(ch[sz]));
cnt[sz] = 0;
ch[u][v] = sz++;
}
u = ch[u][v];
}
cnt[u]++;
if(f)
{
if(cnt[u] == 1)
{
dp[n][1<<n] = len;
id[n++] = u;
}
}
else flag[u] = 1;
//在这里我们可以建立一个int-string的映射,以通过节点序号得知这个点是哪个单词的结尾
}
void getfail()
{
//所有模式串已插入完成
queue<int> q;
per(i, 0,maxnode)
{
if (ch[0][i])
{
fail[ch[0][i]] = 0;
q.push(ch[0][i]);
}
}
while (!q.empty())
{
int now = q.front();
q.pop();
per(i, 0, maxnode)
{
if (ch[now][i])
{
fail[ch[now][i]] = ch[fail[now]][i];
q.push(ch[now][i]);
}
else
ch[now][i] = ch[fail[now]][i];
}
cnt[now] += cnt[fail[now]];
}
}
void Spfa_ForDist(int s) {
int i,j,k,u,v,ndis;
int head = 0,tail = 0;
temp[s] = 0;
spfaqu[head++] = s,in[s] = 1;
while (tail < head) {
u = spfaqu[tail++]; in[u] = 0;
for ( k = 0; k < 2; ++k)
if (!flag[ch[u][k]]) {
ndis = temp[u] + 1;
v = ch[u][k];
if (ndis < temp[v]) {
temp[v] = ndis;
if (in[v] == 0)
spfaqu[head++] = v,in[v] = 1;
}
}
}
}
void build()
{
per(i,0,n)
{
pern(j,0,sz)
{
in[j] = 0;
temp[j] = INF;
}
Spfa_ForDist(id[i]);
per(j,0,n) dist[i][j] = temp[id[j]];
}
}
int solve()
{
per(i,1,(1<<n))
{
per(j,0,n)
{
if(i&(1<<j))
{
per(k,0,n)
{
if(!(i&(1<<k)) && dist[j][k]!=INF)
{
int nst = i|(1<<k);
dp[k][nst] = min(dp[k][nst],dp[j][i]+dist[j][k]);
}
}
}
}
}
int ans = INF;
per(i,0,n) ans = min(ans,dp[i][(1<<n)-1]);
return ans;
}
int main()
{
#ifndef ONLINE_JUDGE
int startTime = clock();
FIN;
#endif
//fastio;
//忘记初始化是小狗
//freopen("out.txt","w",stdout);
//ios::sync_with_stdio(false);
int _T = 0;
while(~scanf("%d%d",&n,&m))
{
if(n+m == 0)return 0;
init();
int k = n;
n = 0;
per(i,0,k){
scanf("%s",s);
insert(s,strlen(s),1);
}
per(i,0,m)
{
scanf("%s",s);
insert(s,strlen(s),0);
}
getfail();
build();
printf("%d\n",solve());
}
#ifndef ONLINE_JUDGE
printf("\nTime = %dms\n", clock() - startTime);
#endif
return 0;
}