昨天状态特别差 看了题解这个题还是一点都不懂 后面自己冷静下来才想通
今天ymy的考试考的照样爆炸 滚粗感
+=inf
+
=
i
n
f
好了现在来看这个题 题意大概是给你一个二叉树, 叶子节点有权值, 可以任意交换每个非叶子节点的左右子树, 最后求逆序对最少是多少
发现线段树合并一般做的是这种树上的儿子对父亲有影响的一类题目
大概这个题目就满足这样的条件
我们可以发现一个性质, 那就是对于一个点的左右子树是否交换只对子树内的逆序对有影响, 而对子树外的逆序对没有影响, 那么我们只需要最小化每一个子树内的逆序对即可, 我们发现在线段树合并的时候, 左区间和左区间合并, 右区间和右区间合并, 我们只有一颗子树的一半区间会影响另一颗子树的另一半区间, 我们类似于CDQ分治一样考虑, 计算没有合并到区间上的影响, 那么每次不交换产生的逆序对数就是
T1[rs].sum×T2[ls].sum
T
1
[
r
s
]
.
s
u
m
×
T
2
[
l
s
]
.
s
u
m
, 交换产生的逆序对数就是
T2[rs].sum×T1[ls].sum
T
2
[
r
s
]
.
s
u
m
×
T
1
[
l
s
]
.
s
u
m
每次递归地合并两颗线段树的时候左区间与左区间合并的时候继续计算, 这样子就可以不重不漏的计算所有逆序对数, 把交换和不交换产生的逆序对数累加起来, 取
min
m
i
n
加入答案就好啦, 时间复杂度
O(nlogn)
O
(
n
l
o
g
n
)
题目链接
Codes
#include<bits/stdc++.h>
#define For(i, a, b) for(register int i = a; i <= b; ++ i)
using namespace std;
typedef long long ll;
const int maxn = 4e5 + 10;
int Root, n, root[maxn], ch[maxn][2], a[maxn], tot;
ll ans, Swp, Uswp;
struct Segment_Tree {
#define ls(x) (T[x].ch[0])
#define rs(x) (T[x].ch[1])
#define mid ((l + r) >> 1)
int cnt;
struct node {
int sum, ch[2];
}T[maxn * 40];
void pushup(int x) {
T[x].sum = T[ls(x)].sum + T[rs(x)].sum;
}
void update(int &x, int l, int r, int p) {
if(!x) x = ++ cnt;
if(l == r) ++ T[x].sum;
else {
if(p <= mid) update(ls(x), l, mid, p);
else update(rs(x), mid + 1, r, p);
pushup(x);
}
}
int merge(int u, int v) {
if(!u || !v) return u + v;
int t = ++ cnt;
Uswp += 1ll * T[rs(v)].sum * T[ls(u)].sum;
Swp += 1ll * T[ls(v)].sum * T[rs(u)].sum;
T[t].sum = T[u].sum + T[v].sum;
ls(t) = merge(ls(u), ls(v));
rs(t) = merge(rs(u), rs(v));
return t;
}
}T;
void dfs(int x) {
if(a[x]) {
T.update(root[x], 1, n, a[x]);
return;
}
dfs(ch[x][0]), dfs(ch[x][1]);
Uswp = Swp = 0;
root[x] = T.merge(root[ch[x][0]], root[ch[x][1]]);
ans += min(Uswp, Swp);
}
void read(int &x) {
x = ++ tot;
scanf("%d", &a[x]);
if(a[x]) return;
read(ch[x][0]);
read(ch[x][1]);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("2212.in", "r", stdin);
freopen("2212.out", "w", stdout);
#endif
scanf("%d", &n);
read(Root);
dfs(Root);
printf("%lld\n", ans);
return 0;
}