【ZOJ 4053】【青岛网络赛主席树+启发式合并】
程序员文章站
2022-05-10 17:14:50
...
题意:
给你一个数组,每次给你一个数,将这个数从整个数组中删去。然后数组被划分成了多个小区间,问你各个区间中最大的逆序对是多少。
思路:
首先建立一颗主席树维护区间[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;
}