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;
}
View Code

 

转载于:https://www.cnblogs.com/hanyuweining/p/11088801.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值