BZOJ2085 [Poi2010]Hamsters
好题!
又学习了一下kmp的用途qwq
我们观察到n非常小
所以肯定可以通过一些东西来求第i个字符串后面接第j个字符串最少需要接几个字符
那就是最长的i的后缀和j的前缀相同的长度
那么我们可以用f[i][j]表示len[j]-x [x表示上面的那个长度]
然后我们现在就是要求走k步的最小长度和
有个特殊的算法就是解决这个的就是倍增floyd
具体倍增floyd的实现 类似于矩阵乘法
理解一下就是说dis每一次都是扩大一倍【走的路的条数
然后方便一点的实现就是建一个虚点0 然后向其他所有点连接长度为len[x]的一条边 这样就不需要额外处理了
//Love and Freedom. #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #define ll long long #define inf 20021225 #define N 201 #define LEN 100010 #define LG 30 using namespace std; char ch[N][LEN]; int nxt[LEN],len[N]; int n; ll m; struct floyd { ll dis[N][N]; floyd(){memset(dis,48,sizeof(dis));} inline ll* operator[](int i){return dis[i];} friend floyd operator*(floyd &x,floyd &y) { floyd z; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) for(int k=0;k<=n;k++) z[i][j]=min(z[i][j],x[i][k]+y[k][j]); return z; } }f,d,ans; int calc(int x,int y) { int lx=len[x],ly=len[y]; for(int i=1;i<=lx;i++) nxt[i]=0; for(int i=2;i<=lx;i++) { nxt[i]=nxt[i-1]; while(nxt[i] && ch[x][i]!=ch[x][nxt[i]+1]) nxt[i]=nxt[nxt[i]]; if(ch[x][i]==ch[x][nxt[i]+1]) nxt[i]++; } if(x==y) return lx-nxt[lx]; int j=0; for(int i=1;i<=ly;i++) { while(j && ch[x][j+1]!=ch[y][i]) j=nxt[j]; if(j<lx && ch[x][j+1]==ch[y][i]) j++; } return lx-j; } floyd ksm(floyd bs,ll mi) { floyd ans; for(int i=0;i<=n;i++) ans[i][i]=0; while(mi) { if(mi&1) ans=ans*bs; bs=bs*bs; mi>>=1; } return ans; } int main() { scanf("%d%lld",&n,&m); f=floyd(); for(int i=1;i<=n;i++) scanf("%s",ch[i]+1),d[0][i]=len[i]=strlen(ch[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[j][i]=calc(i,j); ans=ksm(d,m); ll aans=1ll*inf*inf; for(int i=1;i<=n;i++) aans=min(aans,ans[0][i]); printf("%lld\n",aans); return 0; }