题目描述
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 " , ans); return 0; }