您的位置:首页 > 其它

bzoj3702二叉树 线段树合并

2017-06-03 21:48 288 查看
二倍经验:2212.

很久以前就打了这题,现在回来填坑。。

这题的话是标准的线段树合并。

线段树合并的话,对于一些(大部分)情况,比启发式合并要好很多,时间复杂度少一个log,实现也简单。。

这题来说的话,就是直接合并两个子树,子树中的答案=左子树中的逆序对+右子树中的逆序对+跨越左右的逆序对,前两个扫一遍就可以统计了,问题是最后那个跨越左右子树的逆序对。

假设我们现在合并两颗子树,一颗的根为x,另一颗为y。

由于在合并前,我们已经统计出了x,y的最小逆序对数,我们现在就要统计跨越他们之间的逆序对数量。

然后。。这里我们要对整棵树建权值线段树,保证我们合并的时候的逆序对可以直接计算。

统计逆序对的时候直接枚举是否交换就好了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 400010
#define N 4000100

using namespace std;

int lch
,rch
,sum
;
int root[maxn],l[maxn],r[maxn],w[maxn];
int n,m,num,tot;
long long ans,cnt1,cnt2;

void dfs(int x)
{
scanf("%d",&w[x]);
if (!w[x])
{
l[x]=++num;
dfs(num);
r[x]=++num;
dfs(num);
}
}

void update(int x)
{
sum[x]=sum[lch[x]]+sum[rch[x]];
}

void modify(int &x,int l,int r,int d)
{
if (!x) x=++tot;
if (l==r) {sum[x]=1;return;}
int mid=(l+r)/2;
if (d<=mid) modify(lch[x],l,mid,d);
else modify(rch[x],mid+1,r,d);
update(x);
}

int merge(int x,int y)
{
if (!x) return y;
if (!y) return x;
cnt1+=(long long)sum[rch[y]]*sum[lch[x]];
cnt2+=(long long)sum[rch[x]]*sum[lch[y]];
lch[x]=merge(lch[x],lch[y]);
rch[x]=merge(rch[x],rch[y]);
update(x);
return x;
}

void dfs1(int x)
{
if (!w[x])
{
dfs1(l[x]);
dfs1(r[x]);
cnt1=cnt2=0;
root[x]=merge(root[l[x]],root[r[x]]);
ans+=min(cnt1,cnt2);
}
}

int main()
{

scanf("%d",&n);
num++;dfs(1);
for (int i=1;i<=num;i++)
if (w[i]) modify(root[i],1,n,w[i]);
dfs1(1);
printf("%lld\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: