题目描述
Tz养了一群仓鼠,他们都有英文小写的名字,现在Tz想用一个字母序列来表示他们的名字,只要他们的名字是字母序列中的一个子串就算,出现多次可以重复计算。现在Tz想好了要出现多少个名字,请你求出最短的字母序列的长度是多少。n个字符串保证不互相包含。
输入
输入:第一行n(1<=n<=200)和m(1<=m<=10的9此方),n表示有多少个仓鼠,m表示Tz希望出现名字的次数,接下来n行,每行都是仓鼠的名字(中间没有空格)。
输出
输出:一行,最短的字母序列的长度。
样例输入
4 5
monika
tomek
szymon
bernard
样例输出
23
题解
Hash+倍增Floyd
由于n只有200,并且任意两串不包含。所以可以预处理出某个串后还需要加几个字符可以变成另一个串,可以使用Hash解决。
然后题目要求出现总数为m,相当于要经过m-1个点的最短路径,使用倍增Floyd快速幂求出。
最后的答案为 原串长+最短路 的最小值。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
char str[100010];
ull hash[100010] , base[100010];
int lp[210] , rp[210] , n;
struct data
{
ull v[210][210];
data() {memset(v , 0x3f , sizeof(v));}
data operator*(const data &a)const
{
data ans;
int i , j , k;
for(k = 1 ; k <= n ; k ++ )
for(i = 1 ; i <= n ; i ++ )
for(j = 1 ; j <= n ; j ++ )
ans.v[i][j] = min(ans.v[i][j] , v[i][k] + a.v[k][j]);
return ans;
}
}a , ret;
data pow(data x , int y)
{
data ans;
int i;
for(i = 1 ; i <= n ; i ++ ) ans.v[i][i] = 0;
while(y)
{
if(y & 1) ans = ans * x;
x = x * x , y >>= 1;
}
return ans;
}
int main()
{
int m , i , j , k;
ull ans = 1ull << 63;
scanf("%d%d" , &n , &m);
for(i = 1 ; i <= n ; i ++ )
lp[i] = rp[i - 1] + 1 , scanf("%s" , str + lp[i]) , rp[i] = strlen(str + lp[i]) + lp[i] - 1;
base[0] = 1;
for(i = 1 ; i <= rp[n] ; i ++ )
base[i] = base[i - 1] * 2333 , hash[i] = hash[i - 1] * 2333 + str[i];
for(i = 1 ; i <= n ; i ++ )
for(j = 1 ; j <= n ; j ++ )
for(k = 0 ; k < rp[i] - lp[i] + 1 && k < rp[j] - lp[j] + 1 ; k ++ )
if(hash[rp[i]] - hash[rp[i] - k] * base[k] == hash[lp[j] + k - 1] - hash[lp[j] - 1] * base[k])
a.v[i][j] = rp[j] - lp[j] + 1 - k;
ret = pow(a , m - 1);
for(i = 1 ; i <= n ; i ++ )
for(j = 1 ; j <= n ; j ++ )
ans = min(ans , rp[i] - lp[i] + 1 + ret.v[i][j]);
printf("%llu\n" , ans);
return 0;
}