• P3502 [POI2010]CHO-Hamsters


    这个题的重点是转移方程的优化和匹配字符串的技巧,所以我们分开讲。

    Solution——字符串匹配

    1.KMP算法

    因为是首尾拼接,所以拿KMP算法将nxt数组算出来。可以更简便的运算第 (j) 种字符串接在第 (i) 种结尾的最小增加量。

    for(int k=1;k<=n;k++){
        int j=0;
        for(int i=2;i<=len[k];i++){
            while(j&&s[k][j+1]!=s[k][i]) j=nxt[k][j];
            if(s[k][j+1]==s[k][i]) j++;
            nxt[k][i]=j;
        }
    }
    

    2.AC自动机

    和KMP其实差不多(好像官方正解是这个),AC自动机就是算出fail数组而已,功能一样。

    inline void insert(char *s,int id){
        int len=strlen(s+1),now=0;
        for(int i=1;i<=len;i++){
            int tmp=s[i]-'a';
            if(!ch[now][tmp]) ch[now][tmp]=++tot;
            now=ch[now][tmp];
        }
        ed[now]=id;pos[id]=now;
    }
    
    inline void get_fail()
    {
    	queue<int>q;q.push(0);
    	while(q.size())
    	{
            int x=q.front();q.pop();
            for(int i=0;i<26;i++){
                int y=ch[x][i];
                if(!y) continue;
                int k=fail[x];
                while(k&&(!ch[k][i])) k=fail[k];
                fail[y]=x?ch[k][i]:0;
                q.push(y);
            }
    	}
    }
    

    3.Hash

    先拿的单Hash试了试 ,发现如果模数不太行的话,是会WA好几个点的。

    也可能是我脸黑,一发入魂

    inline bool check(int x,int y,int l){
    	//第三个(ll) 不写会锅4个点 亲测
        return (ll)(Hash[x][len[x]-1]-Hash[x][len[x]-l-1]+mod)%mod
             ==(ll)((ll)Hash[y][l-1]*pow_26[len[x]-l]%mod);
    }
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            int l=Min(len[i],len[j]);
            for(int k(i==j?l-1:l);k;--k)
                if(check(i,j,k)){
                    f[0][i][j]=len[j]-k;
                    break;
                }
            if(f[0][i][j]>10000000) f[0][i][j]=len[j];
        }
    //配合下面的倍增Floyd观看
    

    我最后用的 mod=19280817

    4.双Hash

    和单Hash不一样,对于双Hash来讲,只要你不zz,脸也不算太黑,出题人没有卡你双Hash,应该是没问题的。

    但是,很不幸

    这个题的内存是 (125MB) ,双Hash是会MLE的 〒▽〒

    inline int get_Hash(const int x,const int s,const int e){
        if(s>e) return 0;
        int t=(Hash[x][e+1]-1ll*Hash[x][s]*p26_1[e-s+1]%MOD)+MOD;
        return t>=MOD?t-MOD:t;
    }
    
    inline int get_HASH(const int x,const int s,const int e){
        if(s>e) return 0;
        int t=(HASH[x][e+1]-1ll*HASH[x][s]*p26_2[e-s+1]%mod)+mod;
        return t>=mod?t-mod:t;
    }
    
    
    inline int work(const int x,const int y){
        int m=min(len[x],len[y]);
        if(x==y) --m;
        for(int i=m;~i;--i){
            if((get_Hash(x,len[x]-i,len[x]-1)==get_Hash(y,0,i-1))&&(get_HASH(x,len[x]-i,len[x]-1)==get_HASH(y,0,i-1)))
                return len[y]-i;
        }
        return 0;
    }
    //借用大佬的部分代码
    
    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++)
        	dis[i][j]=work(i,j);
    //这个dis[i][j]对应的是矩阵优化中的dis
    

    Solution——转移方程优化

    1.倍增Floyd

    我们首先转化题意:

    (n) 个点,两个点 (i,j) 之间的权值为将第 (j) 种字符串接在第 (i) 种结尾的最小增加量(注意此处 (i) 可以和 (j) 相等)。求一条经过 (m) 个点的最短路径,起点和终点自己选择。

    暴力求法很好想,因为 (nleq 200) ,所以考虑Floyd。设 (f_{k,i,j})(i)(j) 经过 (k) 个点的最短路径,然后一层一层枚举,直到 (m) ,就求出来了。

    时间复杂度为 (O(n^3 m))

    这不直接满分?

    所以考虑怎么优化——这时倍增Floyd出场了。

    现在需要干啥呢?在 (f_{k,i,j}) 的基础上将意义改为从 (i)(j) 经过 (2^k) 个点的最短路,这样转移的复杂度就从 (O(m)) 变成了 (O(log m))

    for(int t=1;t<=30;t++)
        for(int k=1;k<=n;k++)
            for(int j=1;j<=n;j++)
                for(int i=1;i<=n;i++)
                    f[t][i][j]=Min(f[t-1][i][k]+f[t-1][k][j],f[t][i][j]);
    for(int i=0;i<=30;i++)
        if(m&(1<<i)){
            for(int j=1;j<=n;j++){
                tmp[j]=0x3f3f3f3f3f3f3f3fll;
                for(int k=1;k<=n;k++)
                    tmp[j]=tmp[j]<dis[k]+f[i][k][j]?tmp[j]:dis[k]+f[i][k][j];
            }
            for(int j=1;j<=n;j++)
                dis[j]=tmp[j];
        }
    

    现在时间复杂度为 (O(n^3log m))

    2.矩阵优化

    先考虑不加优化时的朴素做法,即暴力。

    (dp_{i,j}) 表示现在串里有 (i) 个字符串,并以第 (j) 种字符串结尾的最短长度, (dis_{i,j}) 表示将第 (j) 种字符串接在第 (i) 种结尾的最小增加量。

    那么转移方程也很好得到: (dp_{i,j}=min{dp_{i-1,k}+dis_{k,j}}) ,其中初始状态应为 (dp_{1,i}=len_i)

    我们看一下这部分的代码:

    for(int i=2;i=m;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                dp[i][j]=min(dp[i][j],dp[i-1][k]+dis[k][j]);
    

    等等,这一坨东西是不是似曾相识。

    是不是在矩阵乘法中见过一个类似的,这启发我们用矩阵去优化这个转移。

    for(int x=1;x<=n;x++){
        I.a[0][x]=len[x];I.a[x][0]=INF;
        for(int y=1;y<=n;y++){
            int j=0;
            for(inti i=2;i<=len[x];i++){
                whle(j&&s[y][j+1]!=s[x][i]) j=nxt[y][j];
                if(s[y][j+1]==s[x][i]) j++;
                if(i==len[x]) I.a[x][y]=len[y]-j;
            }
        }
    }
    
    for(m--;m;m>>=1){
        if(m&1) Ans=Ans*I;
        I=I*I;
    }
    //搭配上面的KMP观看
    

    时间复杂度也是 (O(n^3log m))

    注意:我将上面的几乎每个组合都试了一遍,除了双Hash在不管无论开不开O2的情况下都是第一个点MLE,别的方法在不开O2的情况是第一个点TLE。(咱也不知道为什么时间复杂度对了还会T,这也卡常?)

    代码

    刚刚每个都放了部分代码,这里就咕咕咕

    还是放一个倍增Floyd的完整代码吧

    ( ̄▽ ̄)*

    #include<bits/stdc++.h>
    #define ll long long
    
    using namespace std;
    const int N=210,L=100010,mod=19260817;
    char s[N][L];
    ll f[35][N][N],dis[N],tmp[N],n,m,ans;
    int len[N],Hash[N][L],pow_26[L];
    
    ll Min(ll x,ll y){
        return x<y?x:y;
    }
    
    inline bool check(int x,int y,int l){
        //第三个(ll) 不写会锅4个点 亲测
        return (ll)(Hash[x][len[x]-1]-Hash[x][len[x]-l-1]+mod)%mod==(ll)((ll)Hash[y][l-1]*pow_26[len[x]-l]%mod);
    }
    
    inline void init(){
        pow_26[0]=1;
        for(int i=1;i<=100000;i++)
            pow_26[i]=pow_26[i-1]*26%mod;
        memset(f,0x3f,sizeof(f));
    }
    
    int main(){
        init();
        scanf("%lld%lld",&n,&m); m--;
        for(int i=1;i<=n;i++){
            scanf("%s",s[i]);
            len[i]=strlen(s[i]);
            dis[i]=len[i];
            for(int j=0;j<len[i];j++)
                if(j) Hash[i][j]=(Hash[i][j-1]+pow_26[j]*(s[i][j]-'a'+1))%mod;
                else Hash[i][j]=s[i][j]-'a'+1;
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++){
                int l=Min(len[i],len[j]);
                for(int k(i==j?l-1:l);k;--k)
                    if(check(i,j,k)){
                        f[0][i][j]=len[j]-k;
                        break;
                    }
                if(f[0][i][j]>10000000) f[0][i][j]=len[j];
            }
        for(int t=1;t<=30;t++)
            for(int k=1;k<=n;k++)
                for(int j=1;j<=n;j++)
                    for(int i=1;i<=n;i++)
                        f[t][i][j]=Min(f[t-1][i][k]+f[t-1][k][j],f[t][i][j]);
        for(int i=0;i<=30;i++)
            if(m&(1<<i)){
                for(int j=1;j<=n;j++){
                    tmp[j]=0x3f3f3f3f3f3f3f3fll;
                    for(int k=1;k<=n;k++)
                        tmp[j]=tmp[j]<dis[k]+f[i][k][j]?tmp[j]:dis[k]+f[i][k][j];
                }
                for(int j=1;j<=n;j++)
                    dis[j]=tmp[j];
            }
        ans=0x3f3f3f3f3f3f3f3fll;
        for(int i=1;i<=n;i++)
            ans=Min(ans,dis[i]);
        printf("%lld
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    LeetCode Related Info
    LeetCode Climbing Stairs
    Linux命令语句秘籍
    Java基础学习 —— io
    Jquery的入门学习
    Java基础学习 —— bat处理文件
    Java基础学习 —— 线程
    Java基础学习——泛型
    java基础学习——集合
    两个div叠加触发事件发生闪烁问题
  • 原文地址:https://www.cnblogs.com/jasony/p/13843283.html
Copyright © 2020-2023  润新知