【bzoj2085】[Poi2010]Hamsters Hash+倍增Floyd
阿新 • • 發佈:2017-08-18
per nbsp amp mon 多次 highlight 需要 spa bernard
題目描述
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; }
【bzoj2085】[Poi2010]Hamsters Hash+倍增Floyd