【ZOJ 4053】【青島網路賽主席樹+啟發式合併】
阿新 • • 發佈:2018-11-19
題意:
給你一個數組,每次給你一個數,將這個數從整個陣列中刪去。然後陣列被劃分成了多個小區間,問你各個區間中最大的逆序對是多少。
思路:
首先建立一顆主席樹維護區間[1,x]的資訊。
然後用set來進行啟發式合併。就是將各個小區間的左端點存入set中,當你需要求出點x在哪個小區間中的時候。只需要在set中進行二分即可找到x所在的區間。
然後問題就變成了我們現在有一個區間,然後區間中的某一個點被去掉了,問剩下兩個區間的逆序對各是多少。假設這個區間是 [l, r],然後現在要將m這個點去掉。並且我們知道區間 [l, r] 當前的逆序對個數 ans 是多少。
則 ans = x2+x3,其中x1是左邊區間中逆序對個數,x2是右邊逆序對個數,x3是左邊區間對整個區間逆序對的貢獻,即對於a[x],[x,r]區間中比x小的數的個數,將這個數累加起來就是x3。
因此我們需要找到小區間,然後暴力求一遍小區間的逆序對個數。然後再暴力求出小區間對整個區間逆序對的貢獻,即可完成此題。
總結:
主要還是主席樹維護區間內的資訊,然後用啟發式合併暴力算區間逆序對完成此題。
程式碼:
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <iostream> #include <set> #define lson l,mid #define rson mid+1,r using namespace std; typedef long long ll; int n,m; const int mx = 1e5+10; int a[mx],root[mx],sma[mx],big[mx]; int ls[20*mx],rs[20*mx],sum[20*mx],size; ll pv[mx]; multiset <ll> mlst; //儲存每個小區間的逆序對,用於求出當前狀態下最多的區間逆序對 set <int> st; void update(int x,int &y,int l,int r,int v,int M) { y = ++size; ls[y] = ls[x],rs[y] = rs[x],sum[y] = sum[x] + v; int mid = (l+r)>>1; if(l==r) return ; if(M<=mid) update(ls[x],ls[y],lson,v,M); else update(rs[x],rs[y],rson,v,M); } int query1(int x,int l,int r,int M) //query1(root[i-1],1,n,a[i]) { if(l>M) return sum[x]; int mid = (l+r)>>1; if(l==r) return 0; int ans = query1(rs[x],rson,M); if(M<mid) ans += query1(ls[x],lson,M); return ans; } int query2(int x,int l,int r,int M) //query2(root[i-1],1,n,a[i]) { if(r<M) return sum[x]; int mid = (l+r)>>1; if(l==r) return 0; int ans = query2(ls[x],lson,M); if(M>mid+1) ans += query2(rs[x],rson,M); return ans; } void init() { sum[0] = ls[0] = rs[0] = size = 0; mlst.clear();st.clear(); mlst.insert(0); st.insert(0);st.insert(n+1); } void deal(int l,int mid,int r) { int L = mid-l,R = r-mid; ll ret = 0,ans = 0; if(L||R){ if(L<=R){ for(int i=l+1;i<mid;i++) ret += big[i]-query1(root[l-1],1,n,a[i]); //ret為小區間內部的逆序對和 if(L) mlst.insert(ret); for(int i=l;i<=mid;i++) ans += query2(root[r],1,n,a[i])-sma[i]; //ans為小區間對整個區間逆序對的貢獻值 mlst.insert(ans = pv[l]-ans); }else{ for(int i=mid+2;i<=r;i++) ans += big[i]-query1(root[mid],1,n,a[i]); if(R) mlst.insert(ans); for(int i=mid;i<=r;i++) ret += big[i]-query1(root[l-1],1,n,a[i]); mlst.insert(ret = pv[l]-ret); } } mlst.erase(mlst.find(pv[l])); pv[l] = ret,pv[mid+1] = ans; //pv[x]表示以x這個點為左端點的區間的逆序對總數 } int main() { int t,cas = 1; scanf("%d",&t); while(t--){ scanf("%d",&n); init(); ll ans = 0; for(int i=1;i<=n;i++) scanf("%d",a+i); for(int i=1;i<=n;i++){ update(root[i-1],root[i],1,n,1,a[i]); //建立主席樹 big[i] = query1(root[i-1],1,n,a[i]); //[1,i]比a[i]大的 sma[i] = query2(root[i-1],1,n,a[i]); //[1,i]比a[i]小的 ans += big[i]; } mlst.insert(ans); pv[1] = ans; for(int i=1;i<=n;i++){ ll ret = *(--mlst.end()); scanf("%d",&m); printf("%lld%c",ret,i==n?'\n':' '); if(ret==0) continue; m = ret ^ m; set<int>::iterator it = st.lower_bound(m), ip = it; deal(*(--ip)+1,m,*it-1); st.insert(m); } } return 0; }