一道线段树合并的题。
首先我们发现,如果我们交换了两棵子树,影响到的逆序对数量只会是这两棵子树交换之后数列改变的逆序对数量,对前面的数列和后面的数列并没有影响,对这两棵子树内部也没有影响,因为逆序对只关注相对位置及数的大小。
据此我们先建树,然后对于每个点整一棵值域线段树维护子树内的数,这个过程可以利用线段树合并完成,然后在合并的时候我们是需要统计逆序对数量的,设两个值 f i r , s e c fir,sec fir,sec, f i r fir fir 表示不换的逆序对数量, s e c sec sec 表示换的逆序对数量。
接下来设 p 1 , p 2 p1,p2 p1,p2 是待合并的两棵树, l ( p 1 / p 2 ) , r ( p 1 / p 2 ) l(p1/p2),r(p1/p2) l(p1/p2),r(p1/p2) 是左右儿子, s u m sum sum 是子树大小, [ q l , q r ] [ql,qr] [ql,qr] 是区间, m i d mid mid 是中点。
那么对于一个区间 [ q l , q r ] [ql,qr] [ql,qr],显然我们换了之后逆序对数量会加上 r ( p 2 ) . s u m × l ( p 1 ) . s u m r(p2).sum \times l(p1).sum r(p2).sum×l(p1).sum,不换会加上 r ( p 1 ) . s u m × l ( p 2 ) . s u m r(p1).sum \times l(p2).sum r(p1).sum×l(p2).sum,但是这样子只会计算 [ q l , m i d ] [ql,mid] [ql,mid] 对于 [ m i d + 1 , q r ] [mid+1,qr] [mid+1,qr] 的逆序对,因此我们每次合并的时候都需要计算一次。
最后每合并完一棵树,我们都取 min { u , v } \min\{u,v\} min{u,v} 加到 a n s ans 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;
}