80: bzoj3705 线段树合并
$des$
现在有一棵二叉树,所有非叶子节点都有两个孩子。在每个叶子节点上有一个权值(有n个叶子节点,满足这些权值为1..n的一个排列)。可以任意交换每个非叶子节点的左右孩子。
要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少。
$sol$
可以发现每次交换对子树内的逆序对数没有影响。所以我们可以使每棵子树都最优。
对每个叶子节点维护一棵权值线段树,自底向上更新.
更新的时候枚举是否需要交换,然后把两棵线段树合并即可.
注意动态开点.
#include<bits/stdc++.h> #define ll long long using namespace std; #define Rep(i, a, b) for(int i = a; i <= b; i ++) #define gc getchar() inline int read() { int x = 0, f = 1; char c = gc; while(c < '0' || c > '9') { if(c == '-') f = -1; c = gc; } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = gc; return x * f; } int n, sz, seg; ll ans, cnt1, cnt2; int v[400005], l[400005], r[400005], root[400005]; int sum[4000005], ls[4000005], rs[4000005]; namespace $ { void readtree(int x) { v[x] = read(); if(!v[x]) { l[x] = ++ sz; readtree(l[x]); r[x] = ++ sz; readtree(r[x]); } } void pushup(int k) { sum[k] = sum[ls[k]] + sum[rs[k]]; } void build(int &k, int l, int r, int val) { if(!k) k = ++ seg; if(l == r) { sum[k] = 1; return; } int mid = (l + r) >> 1; if(val <= mid) build(ls[k], l, mid, val); else build(rs[k], mid + 1, r, val); pushup(k); } int merge(int x,int y) { if(!x) return y; if(!y) return x; cnt1 += (ll)sum[rs[x]] * sum[ls[y]]; cnt2 += (ll)sum[ls[x]] * sum[rs[y]]; ls[x] = merge(ls[x], ls[y]); rs[x] = merge(rs[x], rs[y]); pushup(x); return x; } void solve(int x) { if(!x) return; solve(l[x]); solve(r[x]); if(!v[x]) { cnt1 = cnt2 = 0; root[x] = merge(root[l[x]], root[r[x]]); ans += min(cnt1,cnt2); } } } int main() { n = read(); ++ sz; $:: readtree(1); Rep(i, 1, sz) if(v[i]) $:: build(root[i], 1, n, v[i]); $:: solve(1); cout << ans; return 0; }