研究了挺长时间,只感觉研究出扩展kmp的人真牛。。
首先说一下扩展kmp 的原理
假设已知Next[1...i-1],现在要求Next[i]
设k,1<=k<=i-1,并满足k+Next[k]最大
因为Next[k]表示以第k个字符开始的后缀 与 短串a的最长公共前缀
所以 a[k]--a[k+Next[k]-1]=a[0]--a[Next[k]-1],推出a[i]--a[k+Next[k]-1]=a[i-k]--NExt[A[k]-1]
所以 求Next[i]可以看做求Next[i-k]了,但有特殊的情况,就是实际上Next[i]>Next[i-k],这里需要处理
这里用一个线段表示串
0 i-k Next[k]-1 k i k+Next[k]-1
a: |___|_______|___.............___|___|_______|
这里就出现优化了
令Len=k+Next[k] L=Next[i-k]
当L+i<Len 那么就让 Next[i]=L;
否则 在Next[i]=Len-i-1的基础上 进行匹配
令j=max(0,Len-i),如果a[i+j]==a[j],那么Next[i]++,直到匹配失败
int max(int a,int b)
{
return a>b?a:b;
}
void ExtendedKMP(char *a,char *b,int M,int N,int *Next,int *ret){
int i,j,k;
for(j=0; 1+j<M&&a[j]==a[1+j]; j++);
Next[1]=j;
k=1;
for(i=2; i<M; i++)
{
int Len=k+Next[k],L=Next[i-k];
printf("%d %d %d %d\n",i,k,Len,L+i);
if(L<Len-i)
Next[i]=L;
else {
for(j=max(0,Len-i); i+j<M&&a[i+j]==a[j]; j++)
Next[i]=j;
k=i;
}
}
for(j=0; j<N&&j<M&&a[j]==b[j]; j++);
ret[0]=j;
k=0;
for(i=1; i<N; i++)
{
int Len=k+ret[k],L=Next[i-k];
if(L<Len-i)
ret[i]=L;
else {
for(j=max(0,Len-i); j<M&&i+j<N&&a[j]==b[i+j]; j++);
ret[i]=j;
k=i;
}
}
}
然后说一下1699那个题
这道题可以用暴利做,不过为了学扩展kmp,还是用它做的,不过有了小小的变形
这里cost[i][j]表示s[i]紧接着s[j]的情况下,s[j]可以加到s[i]后面的长度
比如
s[i]=ATGC
s[j]=CTTT
连接后为 ATGCTTT
所以cost[i][j]=3
这个可以用扩展KMP实现
用深搜我花了400多秒,状态DP花了0秒。。。差距真大啊。。
这个题有个坑,就是当一个串是另一个串的字串时,要是用扩展kmp可能出错直接暴力的可能就对了,所以在扩展kmp中加了一点东西,打标记了
深搜:
# include <stdio.h>
# include <string.h>
char s[11][22];
int next[11][22];
int dp[11][11];
int vis[11];
int res;
int max(int a,int b)
{
return a>b?a:b;
}
void ekmp(int x,int m)
{
int i,j,k;
for(j=0; 1+j<m&&s[x][j]==s[x][1+j]; j++);
next[x][1]=j;
k=1;
for(i=2; i<m; i++)
{
int Len=k+next[x][k],L=next[x][i-k];
if(L<Len-i)
next[x][i]=L;
else
{
for(j=max(0,Len-i); i+j<m&&s[x][i+j]==s[x][j]; j++)
next[x][i]=j;
k=i;
}
}
}
int solve(int x,int y,int m,int n) //s[x]为长串 s[y]为短串
{
int i,j,k;
int ret[22];
for(j=0; j<m&&j<n&&s[y][j]==s[x][j]; j++);
ret[0]=j;
k=0;
for(i=1; i<m; i++)
{
int Len=k+ret[k],L=next[y][i-k];
if(L<Len-i)
ret[i]=L;
else
{
for(j=max(0,Len-i); j<n&&i+j<m&&s[y][j]==s[x][i+j]; j++);
ret[i]=j;
k=i;
}
}
int maxret=0;
for(i=0; s[x][i]; i++)
{
if(ret[i]>maxret&&m-i<=ret[i]) maxret=ret[i]; //这里需要注意,保证短串的一部分前缀是长串的后缀,而不是 短串是长串的字串
}
return n-maxret;
}
void dfs(int num,int n,int x,int len)
{
int i;
if(num==n)
{
if(len<res) res=len;
return ;
}
for(i=0; i<n; i++)
{
if(!vis[i])
{
vis[i]=1;
if(num==0) dfs(num+1,n,i,strlen(s[i]));
else dfs(num+1,n,i,len+dp[x][i]);
vis[i]=0;
}
}
}
int main ()
{
int t,n,i,j,tl;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
tl=0;
for(i=0; i<n; i++)
{
scanf("%s",s[i]);
tl+=strlen(s[i]);
ekmp(i,strlen(s[i]));
}
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
dp[i][j]=solve(i,j,strlen(s[i]),strlen(s[j]));
}
}
res=1000000;
dfs(0,n,0,0);
printf("%d\n",res);
}
}
状态DP:
dp[i][j]表示在以s[j]结尾的状态i的最短长度
所以dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+cost[k][j])
这里i^(1<<j) 表示去掉s[j]的状态
# include <stdio.h>
# include <string.h>
char s[11][22];
int next[11][22];
int cost[11][11];
int dp[1<<11][11];
int vis[11];
int res;
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
void ekmp(int x,int m)
{
int i,j,k;
for(j=0; 1+j<m&&s[x][j]==s[x][1+j]; j++);
next[x][1]=j;
k=1;
for(i=2; i<m; i++)
{
int Len=k+next[x][k],L=next[x][i-k];
if(L<Len-i)
next[x][i]=L;
else
{
for(j=max(0,Len-i); i+j<m&&s[x][i+j]==s[x][j]; j++)
next[x][i]=j;
k=i;
}
}
}
int solve(int x,int y,int m,int n) //s[x]为长串 s[y]为短串
{
int i,j,k;
int ret[22];
for(j=0; j<m&&j<n&&s[y][j]==s[x][j]; j++);
ret[0]=j;
k=0;
for(i=1; i<m; i++)
{
int Len=k+ret[k],L=next[y][i-k];
if(L<Len-i)
ret[i]=L;
else
{
for(j=max(0,Len-i); j<n&&i+j<m&&s[y][j]==s[x][i+j]; j++);
ret[i]=j;
k=i;
}
}
int maxret=0;
for(i=0; s[x][i]; i++)
{
if(ret[i]>maxret&&m-i<=ret[i]) maxret=ret[i]; //这里需要注意,保证短串的一部分前缀是长串的后缀,而不是 短串是长串的字串
}
return n-maxret;
}
int main ()
{
int t,n,i,j,tl;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
tl=0;
for(i=0; i<n; i++)
{
scanf("%s",s[i]);
tl+=strlen(s[i]);
ekmp(i,strlen(s[i]));
}
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
cost[i][j]=solve(i,j,strlen(s[i]),strlen(s[j]));
}
}
for(i=0; i<(1<<n); i++)
{
for(j=0; j<n; j++)
{
dp[i][j]=1000000;
if(i&(1<<j)) //表示s[j]未加入状态中
{
if((i^(1<<j))==0) //表示i=0时
dp[i][j]=strlen(s[j]);
else
{
int k;
for(k=0; k<n; k++)
{
dp[i][j]=min(dp[i][j],dp[i^(1<<j)][k]+cost[k][j]);
}
}
}
}
}
int res=1000000;
for(i=0; i<n; i++)
{
res=min(res,dp[(1<<n)-1][i]);
}
printf("%d\n",res);
}
}