1. 程式人生 > 其它 >P3521 [POI2011]ROT-Tree Rotations 題解

P3521 [POI2011]ROT-Tree Rotations 題解

一道線段樹合併的題。

首先我們發現,如果我們交換了兩棵子樹,影響到的逆序對數量只會是這兩棵子樹交換之後數列改變的逆序對數量,對前面的數列和後面的數列並沒有影響,對這兩棵子樹內部也沒有影響,因為逆序對只關注相對位置及數的大小。

據此我們先建樹,然後對於每個點整一棵值域線段樹維護子樹內的數,這個過程可以利用線段樹合併完成,然後在合併的時候我們是需要統計逆序對數量的,設兩個值 \(fir,sec\)\(fir\) 表示不換的逆序對數量,\(sec\) 表示換的逆序對數量。

接下來設 \(p1,p2\) 是待合併的兩棵樹,\(l(p1/p2),r(p1/p2)\) 是左右兒子,\(sum\) 是子樹大小,\([ql,qr]\)

是區間,\(mid\) 是中點。

那麼對於一個區間 \([ql,qr]\),顯然我們換了之後逆序對數量會加上 \(r(p2).sum \times l(p1).sum\),不換會加上 \(r(p1).sum \times l(p2).sum\),但是這樣子只會計算 \([ql,mid]\) 對於 \([mid+1,qr]\) 的逆序對,因此我們每次合併的時候都需要計算一次。

最後每合併完一棵樹,我們都取 \(\min\{u,v\}\) 加到 \(ans\) 裡面,做完了。

這道題有一個坑點就是說如果你不寫空間回收,像我一樣直接動態開點的話空間一定要開大點,不然第一個點會 RE。

GitHub:

CodeBase-of-Plozia

Code:

/*
========= Plozia =========
    Author:Plozia
    Problem:P3521 [POI2011]ROT-Tree Rotations
    Date:2021/12/9
========= Plozia =========
*/

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 6e5 + 5;
int n, Head[MAXN], cnt_Edge = 1, Root[MAXN], r, cnt_SgT, cnt_Node;
LL ans, fir, sec;
struct node { int To, Next; } Edge[MAXN << 1];
struct SgT
{
    int l, r; LL sum;
    #define l(p) tree[p].l
    #define r(p) tree[p].r
    #define s(p) tree[p].sum
}tree[7000005];
// 就是這個地方,我開成 6000005 過不去,7000005 就過了

int Read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
    return sum * fh;
}
LL Max(LL fir, LL sec) { return (fir > sec) ? fir : sec; }
LL Min(LL fir, LL sec) { return (fir < sec) ? fir : sec; }
void add_Edge(int x, int y) { ++cnt_Edge; Edge[cnt_Edge] = (node){y, Head[x]}; Head[x] = cnt_Edge; }

int Read_Tree()
{
    int t = Read();
    if (t != 0) return t;
    else
    {
        t = ++cnt_Node;
        add_Edge(t, Read_Tree());
        add_Edge(t, Read_Tree());
        return t;
    }
}

void Insert(int &p, int x, int l, int r)
{
    if (!p) p = ++cnt_SgT;
    if (l == r) { ++s(p); return ; }
    int mid = (l + r) >> 1;
    if (x <= mid) Insert(l(p), x, l, mid);
    else Insert(r(p), x, mid + 1, r);
    s(p) = s(l(p)) + s(r(p)); return ;
}

int Merge(int p1, int p2, int l, int r)
{
    if (!p1 || !p2) return p1 + p2;
    int p = ++cnt_SgT;
    if (l == r) { s(p) = s(p1) + s(p2); return p; }
    int mid = (l + r) >> 1;
    fir = (fir + s(r(p1)) * s(l(p2)));
    sec = (sec + s(l(p1)) * s(r(p2))); // 注意每次都要計算!
    l(p) = Merge(l(p1), l(p2), l, mid);
    r(p) = Merge(r(p1), r(p2), mid + 1, r);
    s(p) = s(l(p)) + s(r(p)); return p;
}

void dfs(int now, int father)
{
    for (int i = Head[now]; i; i = Edge[i].Next)
    {
        int u = Edge[i].To;
        if (u == father) continue ;
        dfs(u, now); fir = sec = 0;
        Root[now] = Merge(Root[now], Root[u], 0, n);
        ans += Min(fir, sec);
    }
}

int main()
{
    cnt_Node = n = Read(); r = Read_Tree();
    if (n == 1) { puts("0"); return 0; }
    for (int i = 1; i <= n; ++i) Insert(Root[i], i, 0, n);
    dfs(r, r); printf("%lld\n", ans); return 0;
}