树上dsu

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/83447703

现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1…n的一个排列)。可以任意交换每个非叶子节点的左右孩子。 要求进行一系列交换,使得最终所有叶子节点的权值按照遍历序写出来,逆序对个数最少。
n<=200000
这个题其实就是要求出每个非叶子节点的,左儿子中的一个叶子上的值大于右儿子中一个叶子的值,的对数。
树上dsu就是一种启发式合并,就是我们在树形dp时,采用的数据结构只允许父亲利用一个儿子的信息时,最后进入子树大小最大的儿子,来利用子树大小最大的儿子的信息,其他儿子的子树就暴力。可以通过启发式合并的基本理论证明只会有nlogn次暴力。
像这个题,用树状数组存储大儿子的信息,然后在小儿子的子树中暴力查询,总复杂度O(nlog^2n)但是常数。。。实际上和一个log的线段树合并一个速度

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
#define maxn 400005
#define LL long long

int lc[maxn],rc[maxn],val[maxn],n,siz[maxn],rt,st[maxn],ed[maxn],tot;
int tr[maxn];

void dfs(int &now)
{
	now = ++tot;
	scanf("%d",&val[now]);
	st[now] = tot;
	if(!val[now])
	{
		dfs(lc[now]),dfs(rc[now]);
		siz[now] = siz[lc[now]] + siz[rc[now]];
	}
	else siz[now] = 1;
	ed[now] = tot;
}

inline void upd(int now,int val){ for(;now<=n;now+=now&-now) tr[now] += val; }
inline int qsum(int now){int ret=0; for(;now;now-=now&-now) ret+=tr[now]; return ret; }

LL ans;
void ser(int &now)
{
	if(!now) return;
	if(!val[now])
	{
		if(siz[lc[now]] > siz[rc[now]]) swap(lc[now] , rc[now]);
		ser(lc[now]);
		for(int i=st[lc[now]];i<=ed[lc[now]];i++) 
			if(val[i]) 
				upd(val[i],-1);
		ser(rc[now]);
		LL tmp = 0 , res = 1ll * siz[lc[now]] * siz[rc[now]];
		for(int i=st[lc[now]];i<=ed[lc[now]];i++)
			if(val[i])
				tmp += qsum(val[i]);
		res -= tmp;
		ans += min(res , tmp);
		for(int i=st[lc[now]];i<=ed[lc[now]];i++) 
			if(val[i]) 
				upd(val[i],1);
	}
	else upd(val[now],1);
}

int main()
{
	scanf("%d",&n);
	dfs(rt);
	ser(rt);
	printf("%lld\n",ans);
}

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/83447703