洛谷P3502 [POI2010]CHO-Hamsters感想及题解(图论+字符串+矩阵加速$dp\&Floyd$)

洛谷P3502 [POI2010]CHO-Hamsters感想及题解(图论+字符串+矩阵加速\(dp\&Floyd\)

标签:题解
阅读体验:https://zybuluo.com/Junlier/note/1310683

扯闲谈

觉得这是道比较好的引导模型转换的题,就决定写一篇题解
即使我就是看的ZSY的,并且几乎写的一模一样(还是稍有不同的)
安利一发租酥雨的题解
原题地址:洛谷P3502 [POI2010]CHO-Hamsters

先理解题意

给出\(n\)个字符串,让你用这\(n\)个字符串拼接起来,使\(n\)个字符串总的出现次数至少为\(m\),问拼接起来的字符串的最短长度是多少(\(n=200,m=100000\))

很容易想到一个\(n^2m\)\(dp\)

先用\(KMP\)跑出每个字符串接在其他字符串后面的最小代价(增加的最短长度)记为\(dis\)数组

dp[k][i]表示已经出现了\(k\)个字符串且最后一个字符串是\(i\)号串的最短长度

显然直接\(dp\)就行了吧,放一段代码

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

\[显然\min_{i=1}^{n}dp[m][i]就是答案了 \]

转换题型

看到上面那一段转移是不是神似\(Floyd\)?(好吧其实我已开始并不这么觉得
我们把每个字符串抽象成图上的一个点,原来求出的\(dis\)数组看做每对点之间的边(权)
那么是不是我们的问题就转化成了在图上跑\(m\)个点的最短距离
很容易发现为什么上面的\(dp\)那么像\(Floyd\)了。。。

因为存在边界情况:所有字符作为开头时它的代价是len[i]
所以\(dis\)数组相对应有以下更新(新建一个\(0\)号点表示开始节点):dis[0][i]=len[i],dis[i][0]=Inf

矩阵加速

不能完全叫矩阵,只是比较像
考虑到\(dp\)的每一次转移都是一遍\(Floyd\)(每一次转移都是一样的)
你想到了什么?矩阵快速幂优化\(dp!\)
我们之前的矩阵优化都是通过矩阵之间的运算实现的
我们今天运用一个假的运算法则,它叫做\(Floyd\)矩阵运算法则
对于每一次的矩阵乘法改成一次\(Floyd\)运算,就可以顺利的把我们的\(100000\)级别的\(m\)优化掉

也许你还是没有明白为什么可以把\(Floyd\)直接套进矩阵去优化
其实归根结底还是这个转移是没有变化的,且满足结合律
所以进行了很多遍的\(Floyd\)可以直接再和\(ans\)相“乘”(\(ans\)就是答案矩阵了)

代码

洛谷上不开\(O_2\)还是会\(TLE\)。。。

#include<bits/stdc++.h>
#define il inline
#define rg register
#define ldb double
#define lst long long
#define rgt register int
#define N 250
#define M 100050
using namespace std;
const lst Inf=1e18;
il int read()
{
    int s=0,m=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')m=1;ch=getchar();}
    while( isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
    return m?-s:s;
}

int n,m;
int len[N];
int Nxt[N][M];
char S[N][M];
lst Ans=Inf;
struct Matrix{
    lst f[N][N];
    Matrix operator*(const Matrix K)const
        {
            Matrix mid;
            memset(mid.f,63,sizeof(mid.f));
            for(rgt k=0;k<=n;++k)
                for(rgt i=0;i<=n;++i)
                    for(rgt j=0;j<=n;++j)
                        mid.f[i][j]=min(mid.f[i][j],f[i][k]+K.f[k][j]);
            return mid;
        }
}dis,ans;

il void Get_dis()
{
    for(rgt k=1;k<=n;++k)
        for(rgt i=2,j=0;i<=n;++i)
        {
            while(j&&S[k][i]!=S[k][j+1])j=Nxt[k][j];
            if(S[k][i]==S[k][j+1])++j;
            Nxt[k][i]=j;
        }
    //预处理KMP的Nxt[]
    dis.f[0][0]=Inf;
    for(rgt x=1;x<=n;++x)
    {
        dis.f[0][x]=len[x],dis.f[x][0]=Inf;
        for(rgt y=1;y<=n;++y)
            for(rgt i=2,j=0;i<=len[x];++i)
            {
                while(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])dis.f[x][y]=len[y]-j;
            }
    }//预处理两个字符串转化的最小长度
}

int main()
{
    n=read(),m=read()-1;
    for(rgt i=1;i<=n;++i)
        scanf(" %s ",S[i]+1),len[i]=strlen(S[i]+1);
    Get_dis(),ans=dis;
    while(m)
    {
        if(m&1)ans=ans*dis;
        dis=dis*dis,m>>=1;
    }
    for(rgt i=1;i<=n;++i)
        Ans=min(Ans,ans.f[0][i]);
    printf("%lld\n",Ans);return 0;
}

总结一下

模型转化还是比较重要的
考场上几次都没有想到
像遇到这种转化有代价,有要求最小代价的题目
就可以往最短路方面去转化
而遇到\(dp\)无法优化又有转移方程不变这种性质时
可以考虑矩阵快速幂优化\(dp\)

posted @ 2018-10-15 16:36  Eternal风度  阅读(276)  评论(0编辑  收藏  举报
/*自定义地址栏logo*/