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\) 个点的最短路径,起点和终点自己选择。

暴力求法很好想,因为 \(n\leq 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^3\log 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^3\log 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\n",ans);
    return 0;
}
posted @ 2020-10-19 21:36  jasony_sam  阅读(108)  评论(0编辑  收藏  举报