洛谷P3157 [CQOI2011]动态逆序对(树套树/BIT+权值线段树)

题目

n(n<=1e5)个数的数组a[],对应1-n的一个排列,

m(m<=5e4)次操作,每次先询问此时的逆序对数,再删去一个值

思路来源

https://blog.csdn.net/sslz_fsy/article/details/86772265

题解

计删去的ai所在位置pos,考虑逆序对的减少

减少的是pos前面大于ai的数量,和pos后面小于ai的数量

BIT套一下权值线段树就好,每个位置对应一棵1-n的权值线段树

防空间爆,所以实际是主席树动态开点

心得

原来主席树只需要六行就能插链wtcl之前总结的什么破板子

今天彩排的时候闲着无聊,看了一下这个很久之前就听说过但是没学的东西

原来树套树的代码也是这么短,四五十行就搞出来了,之前想复杂了

其实就和之前学的链上建主席树差不多,就是按树状数组lowbit增序建主席树

然后每次更新一个点,都会带来log个点的链更新,每条链最多开log个点,

所以空间是O(n*logn*logn)的,时间也是O(n*logn*logn)的

BIT维护位置,主席树(权值线段树)维护 这个位置不断减lowbit可达的位置 所有位置的值的值域

敲完一发TLE两个点什么鬼,是我常数太大么,然而重在学方法,吸了口O2过了

代码

#include
using namespace std;
typedef long long ll; 
int read()
{
	int cnt=0;char ch=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
}
const int N=1e5+10;
struct node{int l,r,num;}e[N*400];
//N*logn*logn,每次让logn个节点开长为logn的树链 
int n,m,v;
ll ans;
int a[N],rt[N],id[N],tot;//id记录值的位置 
void update(int &p,int l,int r,int pos,int v)
{
	if(!p)p=++tot;
	e[p].num+=v;
	if(l==r)return;
	int mid=(l+r)/2;
	if(pos<=mid)update(e[p].l,l,mid,pos,v);
	else update(e[p].r,mid+1,r,pos,v); 
} 
void add(int x,int pos,int v)//在BIT位置x上加一个v值 
{
	for(int i=x;i<=n;i+=i&-i)
	update(rt[i],1,n,pos,v);
}
int ask(int p,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr)return e[p].num;
	int mid=(l+r)/2,res=0;
	if(ql<=mid)res+=ask(e[p].l,l,mid,ql,qr);
	if(qr>mid)res+=ask(e[p].r,mid+1,r,ql,qr);
	return res;
}
ll sum(int x,int L,int R)//完全逆序序列 逆序对1e10/2 
{
	ll ans=0;
	for(int i=x;i>0;i-=i&-i)
	ans+=ask(rt[i],1,n,L,R);
	return ans;
}
ll cal(int l,int r,int L,int R)//在BIT[l,r]区间内计算值域[L,R]间的数的个数 
{
	if(l>r||L>R)return 0;
	return sum(r,L,R)-sum(l-1,L,R);
}
int main()
{
	n=read();m=read();
	for(int i=1;i<=n;++i)
	{
		v=read();
		ans+=cal(1,i-1,v+1,n);
		add(i,v,1);
		id[v]=i;
	}
	while(m--)
	{
		v=read();
		printf("%lld\n",ans);
		ans-=cal(1,id[v]-1,v+1,n);
		ans-=cal(id[v]+1,n,1,v-1);
		add(id[v],v,-1);
	}
	return 0;
} 

 

你可能感兴趣的:(线段树(权值线段树)/树状数组)