Description
给定 N(N<=200) 个总长度小于 105 的字符串,要求给出一条字符串,其中至少出现 m(m<=109) 次上述给定的字符串(必须是其子串)。保证N各字符串两两不互相包含。求最短的字母序列的长度。
Input
- 第一行n和m,n表示有多少个仓鼠,m表示Tz希望出现名字的次数.。
- 接下来n行,每行都是仓鼠的名字(中间没有空格)。
Output
- 输出一行,最短的字母序列的长度。
Sample Input
4 5
monika
tomek
szymon
bernardSample Output
23
这回基本是对着po大犇的代码敲起来了,负责任地标成转载。
Solution :
首先对于这种字符串衔接的问题,我们一般预先处理出
f[i][j]
,表示在
i
后面接上
在枚举长度的部分似乎并不是 O(N) ,然后可能会导致T?由于对于字符串 stri 与 strj ,如果为了让复杂度尽可能大,则两个字符串必须没有衔接部分,此时一次操作的时间复杂度为 O(min(isize,jsize)) ,总复杂度可以近似看成
但此时又遇到一个问题,发现如果直接对每个字符串都开一个Hash数组,Hash是存不下的,同样如果直接开字符数组,空间复杂度仍然吃不消。显然我们会考虑用string和vector,但是常数问题……(内网神TM开2s)所以学习了Po大犇的指针写法,硬生生卡进了1s。
然后说实在话我就不清楚接下来怎么写了,我写的时候一直在想其他的东西。
构造出上述 f[i][j] 很显然我们是要跑类似于最短路一类的算法的,题目中的“m次出现”应该转化成经过m条路。即题目转化为:
- 给定一张正权值图(所以上述处理 i→i 的情况时不能从最大长度开始枚举),从起点开始要求在图上经过 m 条边,求最短的路径权值总和。
如果跑一般算法就是找到当经过路径数量已经为m后的最小解,复杂度会是类似于
- 定义 f[k][i][j] 为 i→j 且经过 2k 条路的最短路径值。按照倍增的惯例思想,将m可以拆成不同的 2k ,对于不同的路径权值跑一遍 Floyd 即可。那么此处复杂度为 O(N3logM) 。
总结一下倍增 Floyd :
- 求“经过该图M条边的最值”类型的题可以使用倍增 Floyd ,此时为了Floyd,必须满足这个图满足无后向性,即前面走过的路径不会影响接下来的求值。
又get了一个倍增新用法。
#include<bits/stdc++.h>
#define M 202
#define N 200002
#define B 233
#define P 1000000009
#define oo 0x3f3f3f3f3f3f3f3fll
#define inf 0x3f
using namespace std;
int n,m,len[M];
char str[N],*s[M];
int _hash[N],*Hash[M],Base[N];
long long f[M][M],g[M][M],ans[M][M];
int getHash(int *Hash,int L,int R){
return (Hash[R]-1LL*Hash[L-1]*Base[R-L+1]%P+P)%P;
}
int calc(int x,int y){
for(int i=min(len[x],len[y])-(len[y]<=len[x]);~i;--i)
if(getHash(Hash[x],len[x]-i,len[x]-1)==getHash(Hash[y],0,i-1))return i;
return 0;
}
int main(){
scanf("%d %d",&n,&m);
int temp=1,max_len=0;
for(int i=1;i<=n;i++){
scanf("%s",str+temp);
s[i]=str+temp;
Hash[i]=_hash+temp;
len[i]=strlen(s[i]);//范围[temp,temp+len+1)
if(len[i]>max_len)max_len=len[i];
temp+=len[i]+1;
for(int j=0;j<len[i];j++)
Hash[i][j]=(1LL*Hash[i][j-1]*B+s[i][j])%P;//指针访问
}
Base[0]=1;
for(int i=1;i<=max_len;i++)Base[i]=1LL*Base[i-1]*B%P;
memset(f,inf,sizeof(f));
for(int i=1;i<=n;i++){
f[0][i]=len[i];
for(int j=1;j<=n;j++)f[i][j]=len[j]-calc(i,j);
}
memset(ans,inf,sizeof(ans));
for(int i=1;i<=n;i++)ans[i][i]=0;
if(m&1){
memset(g,inf,sizeof(g));
for(int k=0;k<=n;k++)
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(g[i][j]>f[i][k]+ans[k][j])g[i][j]=f[i][k]+ans[k][j];
memcpy(ans,g,sizeof(ans));
}
for(int step=1;(1<<step)<=m;++step){
memset(g,inf,sizeof(g));
for(int k=0;k<=n;k++)
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(g[i][j]>f[i][k]+f[k][j])g[i][j]=f[i][k]+f[k][j];
memcpy(f,g,sizeof(f));//倍增一遍
if(m>>step&1){
memset(g,inf,sizeof(g));
for(int k=0;k<=n;k++)
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
if(g[i][j]>f[i][k]+ans[k][j])g[i][j]=f[i][k]+ans[k][j];
memcpy(ans,g,sizeof(ans));
}
}
long long Ans=oo;
for(int i=1;i<=n;++i)
Ans=min(Ans,ans[0][i]);
cout<<Ans<<endl;
}