题意:给定一颗有 n 个叶节点的二叉树。每个叶节点都有一个权值pi(注意,根不是叶节点),所有叶节点的权值构成了一个1∼n 的排列。对于这棵二叉树的任何一个结点,保证其要么是叶节点,要么左右两个孩子都存在。现在你可以任选一些节点,交换这些节点的左右子树。在最终的树上,按照先序遍历遍历整棵树并依次写下遇到的叶结点的权值构成一个长度为 n的排列,你需要最小化这个排列的逆序对数。
输入格式:第一行是一个整数 n,表示树的叶节点个数。接下来若干行,使用递归的形式来读入这棵树,读入任意一个子树的信息时,初始时读入其根节点。对于一个结点 u,首先有一行一个整数 x。若 x≠0,则表示 u 是一个叶节点,其权值为 x。若 x = 0,则表示 u 不是叶节点,则接下来若干行读入其左子树信息,左子树读入结束后接下来若干行读入其右子树信息。
输出格式:输出一行一个整数表示最小的逆序对数。
分析:假设当前点的左右儿子分别为ls,rs,很容易发现,交换以这两个结点为根节点的子树,并不会影响他们的祖宗交换时逆序对的个数。所以我们可以考虑每一层贪心地进行交换,此时局部最优即全局最优。同时,对于区间[L,R],设其中点为mid,我们只需要考虑这样的逆序对(x,y),满足L ≤ x ≤ mid,mid+1 ≤ y ≤ R即可,并不需要考虑在同一个子树中的逆序对数量。由于这是权值线段树,那么逆序对其实很好统计,对于两颗线段树代表同一段区间的两个节点,考虑交换与不交换两种情况,分别取左、右部分或者右、左部分统计逆序对个数。最后取最小值就可以了。
#include<cstdio> const int maxn = 2e5+5; typedef long long ll; int n, x, cnt; int sum[maxn << 5], ls[maxn << 5], rs[maxn << 5]; ll ans, s1, s2; int rt[maxn]; inline ll min(ll a, ll b) { return a < b ? a : b; } void ins(int &u, int l, int r, int pos){ if(!u) u = ++cnt; ++sum[u]; if(l == r) { return ; } int mid = (l + r) >> 1; if(pos <= mid) ins(ls[u], l, mid, pos); else ins(rs[u], mid + 1, r, pos); } void merge(int &a, int b){ if(!a || !b) { a = a | b; return ;} sum[a] += sum[b]; s1 += 1LL * sum[ls[a]] * sum[rs[b]]; s2 += 1LL * sum[rs[a]] * sum[ls[b]]; merge(ls[a], ls[b]); merge(rs[a], rs[b]); sum[a] = sum[ls[a]] + sum[rs[a]]; } int dfs(){ scanf("%d", &x); if(x) return ins(rt[x], 1, n, x), rt[x]; else{ int ls, rs; ls = dfs(), rs = dfs(); s1 = s2 = 0; merge(ls, rs); ans += min(s1, s2); return ls; } } int main(){ scanf("%d", &n); dfs(); printf("%lld", ans); return 0; }