1. 程式人生 > >線性DP LIS淺談

線性DP LIS淺談

LIS問題

什麼是LIS?

百度百科

最長上升子序列(Longest Increasing Subsequence,LIS),在電腦科學上是指一個序列中最長的單調遞增的子序列。

怎麼求LIS?

O(n^2)做法

具體做法是用兩個for,狀態轉移方程為f[i]=max(f[i],f[j]+1)其中f陣列為這個位置的LIS長度,然後用max找一下最長LIS即可

程式碼

for(int i=1;i<=n;i++)
  for(int j=1;j<i;j++)
  if(a[j]<a[i])
  f[i]=max(f[i],f[j]+1);
  for(int i=1;i<=n;i++)
  ans2=max(ans2,f[i]);

其中也可以取等號

O(nlogn)做法

具體做法是用陣列去維護LIS,然後如果遇到比他小的二分陣列

程式碼

if(u[ans2]<a[i])
    u[++ans2]=a[i];
    else
    u[lower_bound(u+1,u+ans2+1,a[i])-u]=a[i];

拓展定理(Dilworth定理)

什麼是Dilworth定理?

百度百科

反鏈是一種偏序集,其任意兩個元素不可比;而鏈則是一種任意兩個元素可比的偏序集。Dilworth定理說明,存在一個反鏈A與一個將序列劃分為鏈族P的劃分,使得劃分中鏈的數量等於集合A的基數。當存在這種情況時,對任何至多能包含來自P中每一個成員一個元#### 素的反鏈,A一定是此序列中的最大反鏈。同樣地,對於任何最少包含A中的每一個元素的一個鏈的劃分,P也一定是序列可以劃分出的最小鏈族。偏序集的寬度被定義為A與P的共同大小。

另一種Dilworth定理的等價表述是:在有窮偏序集中,任何反鏈最大元素數目等於任何將集合到鏈的劃分中鏈的最小數目。一個關於無限偏序集的理論指出,在此種情況下,一個偏序集具有有限的寬度w,當且僅當它可以劃分為最少w條鏈。

對於LIS的用途

求LIS的個數就是求最長下降子序列的長度,注意其中的<之類的符號是取補集的,也就是說如果上面用的>那麼反面就是<=

完整程式碼(洛谷模板題 P1020 導彈攔截)

O(nlogn)演算法

#include <bits/stdc++.h>
using namespace std;
int a[100005],u[100005],l[100005];
bool cmp(int a,int b)
{
  return a>b;
}
int main()
{
  ios::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int n=1;
  while(cin>>a[n]) n++;
  n--;
  int ans1=1,ans2=1;
  l[1]=u[1]=a[1];
  for(int i=2;i<=n;i++)
  {
    if(l[ans1]>=a[i])
    l[++ans1]=a[i];
    else
    l[upper_bound(l+1,l+ans1+1,a[i],cmp)-l]=a[i];
    if(u[ans2]<a[i])
    u[++ans2]=a[i];
    else
    u[lower_bound(u+1,u+ans2+1,a[i])-u]=a[i];
  }
  cout<<ans1<<" "<<ans2;
}

O(n^2)演算法

#include <bits/stdc++.h>
using namespace std;
int a[100010];
int f[100010];
int main()
{
  int n=1;
  while(cin>>a[n])
  f[n++]=1;
  n--;
  int ans1,ans2;
  ans1=ans2=-1;
  for(int i=1;i<=n;i++)
  for(int j=1;j<i;j++)
  if(a[j]>=a[i])
  f[i]=max(f[i],f[j]+1);
  for(int i=1;i<=n;i++)
  ans1=max(ans1,f[i]);
  for(int i=1;i<=n;i++)
  f[i]=1;
  for(int i=1;i<=n;i++)
  for(int j=1;j<i;j++)
  if(a[j]<a[i])
  f[i]=max(f[i],f[j]+1);
  for(int i=1;i<=n;i++)
  ans2=max(ans2,f[i]);
  cout<<ans1<<" "<<ans2;
}