最長上昇サブシーケンスO(n^2)アルゴリズムとO(nlogn)アルゴリズム
4215 ワード
最長上昇サブシーケンス問題(LIS問題)
O(n^2)アルゴリズム(DP)
解析:1つの数列の最長上昇サブシーケンスについて,a[i]で終わるサブシーケンスの最長上昇シーケンスを1つの配列dp[i]で記録すると,容易に考えられる.
最初の要素にとって、彼のdp[1]=1;
後続の各要素iについて、彼のdp[i]は、前のdp[]の中で最大であるべきであり、次いで、dp[i]=max(dp[j])(j from 1 to i)を加えなければならない.
以上の解析から,1つの数列a[]に対して,a[i]で終わるシーケンスの最長上位昇子シーケンスをdp[i]で表すと,i以前の
0<=j
1つの数列で分割できる最長の上昇しないサブシーケンスの個数を求めると、Dilworthの定理から分かる.
1つの数列の最長上昇しないサブシーケンスの数は、その数列の最長上昇サブシーケンスの長さに等しい.
Dilworthの定理:
定理1)命令(X,≤)は有限バイアスセットであり,rはその最大鎖の大きさである.Xはr個に分けることができるが、これ以上少ない反鎖はできない.
定理2)命令(X,≤)は有限バイアスセットであり,mは反鎖の最大サイズである.Xはm個に分けられるが、これ以上少なくはならない鎖に分けられる.
例えばHDU 1257最小ブロックシステム
もう1つはO(nlogn)のアルゴリズムです
厳密に言えば、このアルゴリズムは二分に属するべきで、DPとはあまり関係がありません.大体このようなものです.
長さiのこの分子配列のうち、ans[]の最小値、すなわちstack[i]=min(a[])(a[]は長さiのすべてのサブ配列の末尾要素からなる配列である)を表す配列stack[i]を定義する.
stack[]があれば、この配列を利用してこの配列の最も長い上昇サブシーケンスを見つけることができ、まずlenをこのシーケンスの最大上昇サブシーケンスとして定義し、len=1;
(1)配列の第1項についてはans[0]で初期化でき,stack[len]=ans[0];
(2)後の各ans[i](i from 1 to n-1)について,forループで遍歴する.
(a)ans[i]>stack[len]の場合、その要素をlenシーケンスに追加し、シーケンス長を更新します:stack[++len]=ans[i];
(b)ans[i]<=stack[len]の場合、ans[i]>stack[j]に1からlenの最大のjを見つけ、stack[j]=ans[i];
これで最終的なlen値が得られ、出力すればよい.
実はこのアルゴリズムもO(n^2)で、ansとstackの2割が遍歴しているので、しかしこのような事実があって、あなたが得たstack配列はずっと単調で非漸減で、それではこの時遍歴は2分で、それによってアルゴリズム全体をO(nlogn)に下げることができます.
例えばPOJ 2533 Longest Ordered Subsequence
長さnのシーケンスを与えて、そのLISの長さを求めます.
lowerでbound()で実装されるコードは次のとおりです.
O(n^2)アルゴリズム(DP)
解析:1つの数列の最長上昇サブシーケンスについて,a[i]で終わるサブシーケンスの最長上昇シーケンスを1つの配列dp[i]で記録すると,容易に考えられる.
最初の要素にとって、彼のdp[1]=1;
後続の各要素iについて、彼のdp[i]は、前のdp[]の中で最大であるべきであり、次いで、dp[i]=max(dp[j])(j from 1 to i)を加えなければならない.
以上の解析から,1つの数列a[]に対して,a[i]で終わるシーケンスの最長上位昇子シーケンスをdp[i]で表すと,i以前の
0<=j
int solve(int a[],int n)
{
int ans=-1;
for(int i=0;i<n;i++)
dp[i]=1;
for(int i=0;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1); // , dp[j]+a[i]
}
ans=max(ans,dp[i]);
}
return ans;
}
1つの数列で分割できる最長の上昇しないサブシーケンスの個数を求めると、Dilworthの定理から分かる.
1つの数列の最長上昇しないサブシーケンスの数は、その数列の最長上昇サブシーケンスの長さに等しい.
Dilworthの定理:
定理1)命令(X,≤)は有限バイアスセットであり,rはその最大鎖の大きさである.Xはr個に分けることができるが、これ以上少ない反鎖はできない.
定理2)命令(X,≤)は有限バイアスセットであり,mは反鎖の最大サイズである.Xはm個に分けられるが、これ以上少なくはならない鎖に分けられる.
例えばHDU 1257最小ブロックシステム
#include <cstdio>
#include <algorithm>
#define MAX 1000
using namespace std;
int main()
{
int n,i,j,ans;
int a[MAX],dp[MAX];
while(scanf("%d",&n)!=-1)
{
ans=0;
for(i=0;i<n;i++)
scanf("%d",&a[i]);
for(i=0;i<n;i++) dp[i]=1;
for(i=0;i<n;i++)
{
for(j=0;j<=i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
printf("%d
",ans);
}
return 0;
}
もう1つはO(nlogn)のアルゴリズムです
厳密に言えば、このアルゴリズムは二分に属するべきで、DPとはあまり関係がありません.大体このようなものです.
長さiのこの分子配列のうち、ans[]の最小値、すなわちstack[i]=min(a[])(a[]は長さiのすべてのサブ配列の末尾要素からなる配列である)を表す配列stack[i]を定義する.
stack[]があれば、この配列を利用してこの配列の最も長い上昇サブシーケンスを見つけることができ、まずlenをこのシーケンスの最大上昇サブシーケンスとして定義し、len=1;
(1)配列の第1項についてはans[0]で初期化でき,stack[len]=ans[0];
(2)後の各ans[i](i from 1 to n-1)について,forループで遍歴する.
(a)ans[i]>stack[len]の場合、その要素をlenシーケンスに追加し、シーケンス長を更新します:stack[++len]=ans[i];
(b)ans[i]<=stack[len]の場合、ans[i]>stack[j]に1からlenの最大のjを見つけ、stack[j]=ans[i];
これで最終的なlen値が得られ、出力すればよい.
実はこのアルゴリズムもO(n^2)で、ansとstackの2割が遍歴しているので、しかしこのような事実があって、あなたが得たstack配列はずっと単調で非漸減で、それではこの時遍歴は2分で、それによってアルゴリズム全体をO(nlogn)に下げることができます.
例えばPOJ 2533 Longest Ordered Subsequence
長さnのシーケンスを与えて、そのLISの長さを求めます.
#include <cstdio>
#include <iostream>
#define MAX 100005
using namespace std;
int ans[MAX],stack[MAX];
int LIS(int temp,int len)
{
int left=0,right=len;
int mid=(left+right)/2;
while(left<=right)
{
if(temp>stack[mid]) left=mid+1;
else right=mid-1;
mid=(left+right)/2;
}
return left;
}
int main()
{
int n,temp,len;
while(scanf("%d",&n)!=-1)
{
for(int i=0;i<n;i++)
scanf("%d",&ans[i]);
len=1;
stack[len]=ans[0];
for(int i=1;i<n;i++)
{
if(ans[i]>stack[len])
stack[++len]=ans[i];
else
{
int j=LIS(ans[i],len);
stack[j]=ans[i];
}
}
printf("%d
",len);
}
return 0;
}
lowerでbound()で実装されるコードは次のとおりです.
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxn 100005
#define inf 0x3f3f3f3f
int dp[maxn];
int a[maxn];
int n;
void solve(){// (LIS)(nlogn)
fill(dp,dp+n,inf);//fill 0~n
for(int i=0;i<n;i++)
{
*lower_bound(dp,dp+n,a[i])=a[i];
// , ,
// dp 。
}
printf("%d
",lower_bound(dp,dp+n,inf)-dp);
}
int main()
{
while(scanf("%d",&n)!=-1)
{
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
solve();
}
return 0;
}