BZOJ2085 POI2010 Hamsters


POI2010 题解整理

Description

给定 N(N<=200) 个总长度小于 105 的字符串,要求给出一条字符串,其中至少出现 m(m<=109) 次上述给定的字符串(必须是其子串)。保证N各字符串两两不互相包含。求最短的字母序列的长度。

Input

  • 第一行n和m,n表示有多少个仓鼠,m表示Tz希望出现名字的次数.。
  • 接下来n行,每行都是仓鼠的名字(中间没有空格)。

Output

  • 输出一行,最短的字母序列的长度。

Sample Input

4 5
monika
tomek
szymon
bernard

Sample Output

23


这回基本是对着po大犇的代码敲起来了,负责任地标成转载。


Solution

首先对于这种字符串衔接的问题,我们一般预先处理出 f[i][j] ,表示在 i 后面接上j需要的代价。但如果直接暴力匹配过来, O(N4) 的复杂度就有点吃不消了。所以我们采用Hash优化一维 O(N)

在枚举长度的部分似乎并不是 O(N) ,然后可能会导致T?由于对于字符串 stri strj ,如果为了让复杂度尽可能大,则两个字符串必须没有衔接部分,此时一次操作的时间复杂度为 O(min(isize,jsize)) ,总复杂度可以近似看成

O(i=1Nmin(isize,jsize)N)O(MN)
所以复杂度不会导致T的,可以直接上去搞。

但此时又遇到一个问题,发现如果直接对每个字符串都开一个Hash数组,Hash是存不下的,同样如果直接开字符数组,空间复杂度仍然吃不消。显然我们会考虑用string和vector,但是常数问题……(内网神TM开2s)所以学习了Po大犇的指针写法,硬生生卡进了1s。

然后说实在话我就不清楚接下来怎么写了,我写的时候一直在想其他的东西。

构造出上述 f[i][j] 很显然我们是要跑类似于最短路一类的算法的,题目中的“m次出现”应该转化成经过m条路。即题目转化为:

  • 给定一张正权值图(所以上述处理 ii 的情况时不能从最大长度开始枚举),从起点开始要求在图上经过 m 条边,求最短的路径权值总和。

如果跑一般算法就是找到当经过路径数量已经为m后的最小解,复杂度会是类似于O(NMlogN)这种的,显然不行。但是,当跑过一些路径到达某个点node时,这个点之后走step步的最短距离与之前的行走是无关的,即满足无后向性。此时我们就可以使用倍增 Floyd 去优化算法了:

  • 定义 f[k][i][j] ij 且经过 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值