C++ 最長遞增子序列問題
阿新 • • 發佈:2018-12-24
最長遞增子序列問題:在一列數中尋找一些數,這些數滿足:任意兩個數a[i]和a[j],若i
動態規劃
求一序列的最長子序列的長度,那麼將問題變為在序列中求以a[k]為終點的最長上升子序列的長度aLen[k],狀態轉移方程為:
aLen[0] = 1
aLen[k] = MAX{aLen[i] ,0 <= i < k且a[i] < a[k]} + 1
因為在a[k]左邊結尾小於a[k]的序列的長度的最大值加上一就是以a[k]結尾的序列的最大長度。
const int MAX = 10000;
int a[MAX];//原始資料
int aLen[MAX];//存以第i個數為終點的序列長度
int Dynamic(int *a,int *aLen,int l)
{
int n,m;
int maxLen = 1;
for(n = 0;n < l;++n)
aLen[n] = 1;
for(n = 1;n< l;++n)
{
aLen[n] = 1;//最小為一
for(m = 0;m < n;++m)//尋找以a[n]結尾的最長序列(末尾比a[n]小且長度最長的序列)
if(a[n] > a[m] && aLen[m] + 1 > aLen[n])
aLen[n] = aLen[m] + 1 ;
if(aLen[n] > maxLen)//標記當前的最長序列
maxLen = aLen[n];
}
return maxLen;
}
int main()
{
int l;
for(int i = 0;i < MAX;++i)
{
l = rand()%20;
a[i] = l;
cout << l << endl;
}
cout << "answer:" <<Dynamic(a,aLen,MAX);
}
改進版
原文
考慮兩個數a[x]和a[y],x < y且a[x] < a[y],且dp[x]=dp[y],當a[t]要選擇時,到底取哪一個構成最優的呢?顯然選取a[x]更有潛力,因為可能存在a[x] < a[z] < a[y],這樣a[t]可以獲得更優的值。在這裡給我們一個啟示,當dp[t]一樣時,儘量選擇更小的a[x].
按dp[t]=k來分類,只需保留dp[t]=k的所有a[t]中的最小值,設d[k]記錄這個值,d[k]=min{a[t],dp[t]=k}。
這時注意到d的兩個特點(重要):
1. d[k]在計算過程中單調不升;
2. d陣列是有序的,d[1] < d[2] < ..d[n]。
利用這兩個性質,可以很方便的求解:
1. 設當前已求出的最長上升子序列的長度為len(初始時為1),每次讀入一個新元素x:
2. 若x>d[len],則直接加入到d的末尾,且len++;(利用性質2)
否則,在d中二分查詢,找到第一個比x小的數d[k],並d[k+1]=x,在這裡x<=d[k+1]一定成立(性質1,2)。
/**
最長遞增子序列O(nlogn)演算法:
狀態轉移方程:f[i] = max{f[i],f[j]+1},1<=j<i,a[j]<a[i].
分析:加入x<y,f[x]>=f[y],則x相對於y更有潛力。
首先根據f[]值分類,記錄滿足f[t]=k的最小的值a[t],記d[k]=min{a[t]},f[t]=k.
1.發現d[k]在計算過程中單調不上升
2.d[1]<d[2]<...<d[k] (反證) 1 2 3 8 4 7
解法:
1. 設當前最長遞增子序列為len,考慮元素a[i];
2. 若d[len]<a[i],則len++,並將d[len]=a[i];
否則,在d[0-len]中二分查詢,找到第一個比它小的元素d[k],並d[k+1]=a[i].()
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 41000;
int a[N]; //a[i] 原始資料
int d[N]; //d[i] 長度為i的遞增子序列的最小值
int BinSearch(int key, int* d, int low, int high)
{
while(low<=high)
{
int mid = (low+high)>>1;
if(key>d[mid] && key<=d[mid+1])
return mid;
else if(key>d[mid])
low = mid+1;
else
high = mid-1;
}
return 0;
}
int LIS(int* a, int n, int* d)
{
int i,j;
d[1] = a[1];
int len = 1; //遞增子序列長度
for(i = 2; i <= n; i++)
{
if(d[len]<a[i])
j = ++len;
else
j = BinSearch(a[i],d,1,len) + 1;
d[j] = a[i];
}
return len;
}
int main()
{
int t;
int p;
scanf("%d",&t);
while(t--)
{
scanf("%d",&p);
for(int i = 1; i <= p; i++)
scanf("%d",&a[i]);
printf("%d\n",LIS(a,p,d));
}
return 0;
}