P3521 [POI2011]ROT-Tree Rotations 题解
一道线段树合并的题。
首先我们发现,如果我们交换了两棵子树,影响到的逆序对数量只会是这两棵子树交换之后数列改变的逆序对数量,对前面的数列和后面的数列并没有影响,对这两棵子树内部也没有影响,因为逆序对只关注相对位置及数的大小。
据此我们先建树,然后对于每个点整一棵值域线段树维护子树内的数,这个过程可以利用线段树合并完成,然后在合并的时候我们是需要统计逆序对数量的,设两个值 , 表示不换的逆序对数量, 表示换的逆序对数量。
接下来设 是待合并的两棵树, 是左右儿子, 是子树大小, 是区间, 是中点。
那么对于一个区间 ,显然我们换了之后逆序对数量会加上 ,不换会加上 ,但是这样子只会计算 对于 的逆序对,因此我们每次合并的时候都需要计算一次。
最后每合并完一棵树,我们都取 加到 里面,做完了。
这道题有一个坑点就是说如果你不写空间回收,像我一样直接动态开点的话空间一定要开大点,不然第一个点会 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具