【Luogu P1439】最长公共子序列(LCS)
Luogu P1439
令f[i][j]表示a的前i個元素與b的前j個元素的最長公共子序列
可以得到狀態轉移方程:
if (a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1;
dp[i][j]=max(dp[i][j],dp[i-1][j],dp[i][j-1]);
時空復雜度都為O(n2)
對于本題這種做法顯然是無法接受的。
我們可以對這個題目進行轉化。仔細看題,可以發現a,b兩個序列都是1-n的排列。
那么,我們可以利用映射,將a中的數一一映射成為1,2,3,4,5……,n
再把b中的數一一對應更改。由于a中的數是升序的,所以b中的最長上升子序列的長度就是a與b的最長公共子序列。LCS問題就轉化成了LIS問題。
例如樣例
a的 3 2 1 4 5映射為1 2 3 4 5
則b從1 2 3 4 5變為3 2 1 4 5
結合上面的分析就會變得很容易理解了。
#include<algorithm>
#include<cstdio>
using namespace std;
int n,a[100005],b[100005],k[100005],dp[100005],ans;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
k[a[i]]=i;
}
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
b[i]=k[b[i]];
}
dp[1]=1;
for (int i=2;i<=n;i++)
{
for (int j=1;j<i;j++)
{
if (b[i]>b[j]) dp[i]=max(dp[i],dp[j]+1);
else dp[i]=max(1,dp[i]);
}
}
for (int i=1;i<=n;i++)
ans=max(ans,dp[i]);
printf("%d",ans);
return 0;
}
這個動態規劃可以很輕松地寫出來,但是我們發現時間還是不夠優秀。
那么我們就要對這個算法進行優化。對于LIS問題,有一種廣為人知的O(nlogn)的解法。
dp[i]中存儲長度為i的LIS的最后一個數。
如果符合單調上升就直接增長長度并記錄,否則就利用STL二分查找出dp數組中第一個大于b[i]的位置,替換它。
舉個例子,例如 3 6 2 4 7 8
一開始的序列{3},接著變成{3,6}
遇到2之后我們將3替換{2,6},為什么可以進行替換呢?
因為后面還有一個4可以替換掉6,構成一條更優的序列。(保證結尾盡可能小,就能保證序列盡可能優)
如果后面沒有4呢?那么也沒有關系,因為這個2即使修改了也對答案沒有任何影響。
(想一想為什么)
dp[1]=b[1];
len=1;
for (int i=2;i<=n;i++)
{
if (b[i]>dp[len]) dp[++len]=b[i];//記錄并增長長度。
else
{
int x=upper_bound(dp+1,dp+1+len,b[i])-dp;
dp[x]=b[i];
//利用STL二分查找出dp數組中第一個大于b[i]的位置,替換它。
}
}
完整代碼如下:
#include<algorithm>
#include<cstdio>
using namespace std;
int n,a[100005],b[100005],k[100005],dp[100005],ans,len;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
k[a[i]]=i;//映射
}
for (int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
b[i]=k[b[i]];//對應修改
}
dp[1]=b[1];
len=1;
for (int i=2;i<=n;i++)
{
if (b[i]>dp[len]) dp[++len]=b[i];
else
{
int x=upper_bound(dp+1,dp+1+len,b[i])-dp;
dp[x]=b[i];
}
}
printf("%d",len);
return 0;
}
總結
以上是生活随笔為你收集整理的【Luogu P1439】最长公共子序列(LCS)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数学建模第四节2020.4.24-5.3
- 下一篇: BZOJ:2190: [SDOI2008