1. 程式人生 > >【ZOJ 4053】【青島網路賽主席樹+啟發式合併】

【ZOJ 4053】【青島網路賽主席樹+啟發式合併】

題意:

      給你一個數組,每次給你一個數,將這個數從整個陣列中刪去。然後陣列被劃分成了多個小區間,問你各個區間中最大的逆序對是多少。

 

思路:

      首先建立一顆主席樹維護區間[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;
}