1. 程式人生 > >【BZOJ】3173: [Tjoi2013]最長上升子序列(樹狀數組)

【BZOJ】3173: [Tjoi2013]最長上升子序列(樹狀數組)

nss 貢獻 isp 轉化 復雜 src printf efi col

【題意】給定ai,將1~n從小到大插入到第ai個數字之後,求每次插入後的LIS長度。

【算法】樹狀數組||平衡樹

【題解】

這是樹狀數組的一個用法:O(n log n)尋找前綴和為k的最小位置。(當數列中只有0和1時,轉化為求對應排名的數字,就是簡單代替平衡樹)

根據樹狀數組的二進制分組規律,從大到小進行倍增,可以發現每次需要加的Σa[i],i∈(now,now+(1<<i)]剛好就是c[now+(1<<i)]。

文字表述就是,跳躍到的位置的c[]剛好表示中間跳躍的數字和,這是樹狀數組二進制分組規律的特殊性質。

還需要註意的是,實際上需要尋找前綴和<k的最大位置,最後+1。(否則會被目標數字後面的0幹擾)

利用上述的方法,初始樹狀數組全部置為1,然後從n到1倒著尋找並刪除,就可以得到每個數字在最終序列中的位置。

這道題由於從小到大插入,可以發現將所有數字全部插入也不會破壞過程中需要的LIS(只會在最後增長)。

那麽第i個答案就是以數字1~i結尾的LIS的最長長度。

所以令f[i]表示最終序列中以數字 i 結尾的LIS,則第i個答案就是min(f[j]),j=1~i。(是數字i,不是第i個位置)

求解f[i]只需在O(n log n)求解整個最終序列的LIS的過程中求出即可。

總復雜度O(n log n)。

技術分享圖片
#include<cstdio>
#include<cstring>
#include
<algorithm> #include<cctype> #define lowbit(x) (x&-x) using namespace std; const int maxn=100010; int a[maxn],b[maxn],c[maxn],g[maxn],anss[maxn],n; int read(){ char c;int s=0,t=1; while(!isdigit(c=getchar()))if(c==-)t=-1; do{s=s*10+c-0;}while(isdigit(c=getchar())); return
s*t; } void insert(int x,int k){for(int i=x;i<=n;i+=lowbit(i))c[i]+=k;} int find(int x){ int now=0,ans=0; for(int i=20;i>=0;i--){ now+=(1<<i); if(now<n&&ans+c[now]<x)ans+=c[now];//< near else now-=(1<<i); } now++; insert(now,-1); return now; } int max(int a,int b){return a<b?b:a;} int main(){ n=read(); for(int i=1;i<=n;i++){ a[i]=read(); c[i]++;c[i+lowbit(i)]+=c[i]; } for(int i=n;i>=1;i--)b[find(a[i]+1)]=i; int m=0; for(int i=1;i<=n;i++){ int s=lower_bound(g+1,g+m+1,b[i])-g; if(s>m)g[++m]=b[i];else g[s]=b[i]; anss[b[i]]=s; } for(int i=1;i<=n;i++){ anss[i]=max(anss[i-1],anss[i]); printf("%d\n",anss[i]); } return 0; }
View Code

最後,代碼中運用的線性構造樹狀數組,原理十分簡單。

首先要求1~n都有數字(0也行),然後每個數加到自身c[i]+=a[i],再貢獻一下父親c[i+lowbit(i)]+=c[i]就可以了。

【BZOJ】3173: [Tjoi2013]最長上升子序列(樹狀數組)