溫故而知新——BOM複習
LIS(Longest Increasing Subsequence)即最長上升子序列
給定一個序列,LIS是指其所有上升子序列中最長的一個
舉個例子a={100,98,300,385,200,166}其最長上升子序列為{100,300,385}
樸素dp求法O(n*n)
演算法描述
f[i]指以i為起點的LIS長度(視情況而定)。列舉起點,起點之後的原序列,如果a[j]>a[i]則f[i]可能是以j為起點的LIS+1,即f[i]=max(f[i],f[j]+1)。此處pre陣列用來記錄前驅,當前的i是由j轉移過來的。
由於f[i]要由f[i+1---n]更新而來,所以列舉起點要倒序
Code for(int i=n;i>0;--i){ for(int j=i;j<=n;++j){ if(a[j]>a[i]){ if(f[i]<f[j]+1){ pre[i]=j;f[i]=f[j]+1; } } } }
貪心+二分查詢O(n*log(n))
演算法描述
LIS陣列記錄可能的LIS,len表示LIS長度。掃描整個陣列,如果a[i]大於LIS[len],則在末尾插入a[i];否則就在LIS中找到第一個比a[i]大的數,用a[i]將其替換,此處用到了stl upper_bound();
模擬
a={100,98,300,385,200,166}
i=1 LIS={100}
i=2 LIS={98}這裡把100替換是為了讓更多的數放進來,體現了貪心思想
i=3 LIS={98,300}
i=4 LIS={98,300,385}
i=5 LIS={98,200,385}
i=6 LIS={98,166,385}
很顯然,這裡求出的LIS是錯誤的,但是其保證了單調性,len是正確的
for(int i=1;i<=n;++i){
if(a[i]>LIS[len]){
LIS[++len]=a[i];
}
else{
int pos=upper_bound(LIS+1,LIS+len+1,a[i])-LIS;
LIS[pos]=a[i];//LIS不是正確的序列,但長度是正確的
}
}
關於upper_bound()和lower_bound()
upper_bound(start,end+1,data)。start為查詢範圍左端點的指標,end為查詢範圍右端點的指標,upper_bound會返回範圍中第一個大於
lower_bound(start,end+1,data)。start為查詢範圍左端點的指標,end為查詢範圍右端點的指標,lower_bound會返回範圍中第一個大於等於data的數的指標,如果找不到就返回end
過載
upper_bound(start,end+1,data,greater())
upper_bound會返回範圍中第一個小於data的數的指標,如果找不到就返回end+1
lower_bound(start,end+1,data,greater())
lower_bound會返回範圍中第一個小於等於data的數的指標,如果找不到就返回end+1
在結構體中的使用過載一下小於號就行了
update 2022 1 25
樹狀陣列維護LIS
x是原陣列內某個值
c[x]定義為以x結尾的LIS長度
本質上是O(n^2)做法的優化,也就是說可以記錄路徑
程式碼可能是錯的,退役了,先鴿著
void update(int x,int data){
for(int i=x;i<=n;i+=lowbit(x)){
if(c[x]+1>c[i]){
c[i]=c[x]+1;
pre[i]=x;
}
}
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(x)){
res=max(res,c[i]);
}
return res;
}
void work(){
n=read();
for(int i=1;i<=n;++i){
b[i]=a[i]=read();
}
//離散化
sort(b+1,b+n+1);
int len=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i){
a[i]=lower_bound(b+1,b+len+1,a[i])-b;
}
for(int i=1;i<=len;++i)c[a[i]]=1;
for(int i=1;i<=len;++i){
c[a[i]]=query(a[i]-1);
update(a[i]);
}
}