【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;
}

猜你喜欢

转载自blog.csdn.net/qq_41552508/article/details/84197202
ZOJ