洛谷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]就是答案了\]
轉換題型
看到上面那一段轉移是不是神似
我們把每個字串抽象成圖上的一個點,原來求出的\(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\)