BZOJ 2212 & POI 18 Tree Rotations(线段树合并)

题意:给定一棵2n-1个节点的二叉树, 每个叶子上有1~n的数字, 保证每个数字出现且仅出现一次

 允许任意次交换某两棵兄弟子树

 对交换完毕的树求先序遍历, 形成1~n的一个排列

 求这个排列最小的逆序对个数

 1 ≤ n ≤ 2 * 1e5 (1e6)



思路:子树x内的逆序对个数为 :x 左子树内的逆序对个数 + x 右子树内的逆序对个数 + 跨越 x 左子树与右子树的逆

序对数。可以发现左右子树内部的逆序对与是否交换左右子树无关,是否交换左右子树取决于交换后 “跨越 x 左子树

与右子树的逆序对” 是否会减小。


可以用一些统计区间内数字个数 (cnt) 的线段树的合并来解决

在merge的过程中统计交换与不交换产生的逆序对数

a属于T的左子树, b属于T的右子树, ans0为不交换产生的逆序对数, ans1为交换产生的逆序对数

merge(a, b):
如果a, b中有一个为空, 就返回另一个
ans0 += cnt(a -> r) * cnt(b -> l)
ans1 += cnt(a -> l) * cnt(b -> r)
返回merge(a->l, b->l)与merge(a->r, b->r)连接形成的树的根


代码:

#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn = 4e5+5;
const int maxnode = 4e6+5;
int n, sz, seg;
ll ans, ans0, ans1;
int tree[maxnode], lch[maxnode], rch[maxnode];
int v[maxn], l[maxn], r[maxn], root[maxn];

void init()
{
    sz = 1;
    ans = seg = 0;
    memset(v, 0, sizeof(v));
    memset(root, 0, sizeof(root));
    memset(tree, 0, sizeof(tree));
    memset(lch, 0, sizeof(lch));
    memset(rch, 0, sizeof(rch));
}

void readTree(int x)
{
    scanf("%d", &v[x]);
    if(!v[x])
    {
        l[x] = ++sz;
        readTree(l[x]);
        r[x] = ++sz;
        readTree(r[x]);
    }
}

void pushup(int rt)
{
    tree[rt] = tree[lch[rt]]+tree[rch[rt]];
}

void build(int &rt, int l, int r, int val)
{
    if(!rt) rt = ++seg;
    if(l == r)
    {
        tree[rt] = 1;
        return ;
    }
    int mid = (l+r)/2;
    if(val <= mid) build(lch[rt], l, mid, val);
    else build(rch[rt], mid+1, r, val);
    pushup(rt);
}


int merge(int x, int y)
{
    if(!x) return y;
    if(!y) return x;
    ans0 += (ll)tree[rch[x]]*tree[lch[y]];
    ans1 += (ll)tree[lch[x]]*tree[rch[y]];
    lch[x] = merge(lch[x], lch[y]);
    rch[x] = merge(rch[x], rch[y]);
    pushup(x);
    return x;
}

void solve(int x)
{
    if(!x) return ;
    solve(l[x]), solve(r[x]);
    if(!v[x])
    {
        ans0 = ans1 = 0;
        root[x] = merge(root[l[x]], root[r[x]]);
        ans += min(ans0, ans1);
    }
}

int main(void)
{
    while(cin >> n)
    {
        init();
        readTree(1);
        for(int i = 1; i <= sz; i++)
            if(v[i]) build(root[i], 1, n, v[i]);
        solve(1);
        printf("%lld\n", ans);
    }
    return 0;
}


你可能感兴趣的:(线段树)