1. 程式人生 > >洛谷P3502 [POI2010]CHO-Hamsters感想及題解(圖論+字串+矩陣加速$dp\&Floyd$)

洛谷P3502 [POI2010]CHO-Hamsters感想及題解(圖論+字串+矩陣加速$dp\&Floyd$)

洛谷P3502 [POI2010]CHO-Hamsters感想及題解(圖論+字串+矩陣加速\(dp\&Floyd\)

扯閒談

覺得這是道比較好的引導模型轉換的題,就決定寫一篇題解
即使我就是看的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\)