【BZOJ2212】【POI2011】【XSY2014】Tree Rotation(线段树合并)

输入格式真的毒瘤

权值线段树合并。

我们先对每一个叶子建一棵权值线段树,并把它自己的权值插入到里面。

我们不妨设原树中当前节点为 u u u,爸爸 f a fa fa,左儿子 l c lc lc,右儿子 r c rc rc,那么显然这棵树中的逆序对分为 3 3 3 个部分: l c lc lc 里的逆序对, r c rc rc 里的逆序对和跨 l c lc lc r c rc rc 的逆序对。

而我们现在已经把 l c lc lc 里的逆序对和 r c rc rc 里的逆序对算好了,现在需要求出跨 l c lc lc r c rc rc 的逆序对,也就是合并 l c lc lc r c rc rc 。然后我们发现,无论 l c lc lc 内的子树怎么换来换去, r c rc rc内的子树怎么换来换去,都是对跨 l c lc lc r c rc rc 的逆序对个数没有影响的。

同理,无论我们交不交换 l c lc lc r c rc rc,对 f a fa fa 那一层的合并也是没有影响的。

所以对于跨 l c lc lc r c rc rc 的逆序对,我们分交换 l c lc lc r c rc rc 和不交换的情况算出两种情况的逆序对个数,再取较小值就好了。

我们遍历两棵权值线段树来暴力合并两棵权值线段树。

那么对于 l c lc lc r c rc rc 权值线段树中同一位置的点 a a a b b b,若不交换 l c lc lc r c rc rc,显然 l c lc lc 中的点的编号(这里的编号是指前序遍历的先后顺序)肯定都在 r c rc rc 前, a a a 右儿子的权值必定大于 b b b 左儿子的权值,所以 s i z e [ c h [ a ] [ 1 ] ] ∗ s i z e [ c h [ b ] [ 0 ] ] size[ch[a][1]]*size[ch[b][0]] size[ch[a][1]]size[ch[b][0]] 就是这一位置的答案了。

同理,如果交换 l c lc lc r c rc rc,这一位置的答案就是 s i z e [ c h [ a ] [ 0 ] ] ∗ s i z e [ c h [ b ] [ 1 ] ] size[ch[a][0]]*size[ch[b][1]] size[ch[a][0]]size[ch[b][1]]

最后合并完统计答案即可。

代码如下:

#include<bits/stdc++.h>

#define N 200010
#define int long long

using namespace std;

struct Tree
{
	int ch[2],size;
}t[N<<5];

int n,tot,ans,num1,num2;

int update(int l,int r,int val)
{
	int u=++tot;
	t[u].size=1;
	if(l==r) return u;
	int mid=(l+r)>>1;
	if(val<=mid) t[u].ch[0]=update(l,mid,val);
	else t[u].ch[1]=update(mid+1,r,val);
	return u;
}

int merge(int a,int b,int l,int r)//把b合并至a
{
	if(!a||!b) return a+b;
	if(l==r)
	{
		t[a].size+=t[b].size;
		return a;
	}
	num1+=t[t[a].ch[1]].size*t[t[b].ch[0]].size;//不交换的答案
	num2+=t[t[b].ch[1]].size*t[t[a].ch[0]].size;//交换后的答案
	int mid=(l+r)>>1;
	t[a].ch[0]=merge(t[a].ch[0],t[b].ch[0],l,mid);
	t[a].ch[1]=merge(t[a].ch[1],t[b].ch[1],mid+1,r);
	t[a].size+=t[b].size;
	return a;
}

int dfs()
{
	int u,val;
	scanf("%lld",&val);
	if(!val)
	{
		int lc=dfs(),rc=dfs();
		num1=num2=0;
		u=merge(lc,rc,1,n);
		ans+=min(num1,num2);//ans加上较小值
	}
	else u=update(1,n,val);
	return u;
}

signed main()
{
	scanf("%lld",&n);
	dfs();
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值