扩展kmp poj1699

研究了挺长时间,只感觉研究出扩展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实现

接下来就可以用深搜做或者状态DP做啦。。

用深搜我花了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);
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值