[ABC337G] Tree Inversion(换根 dp + 值域线段树)

link

题目形式就很换根 dp

如果这种题用朴素的做法求,就是暴力以每个点都做一次根跑树,自底向上统计,时间是 O(n2)

而换根 dp 的思想就是分两步,

一般先钦定某个点(如 1)为根,统计一遍以 1 为根时的结果,

然后挖掘如果以其他点为根时,变换对结果的影响,一般就是自顶向下更新如果换根后的信息

这样分成两次 dfs,时间就是线性的 O(n)


这个题发现就是求类似树上逆序对的个数,即在同一个子树里,节点编号和深度大小关系相反

考虑第一步,设 f[x] 表示以节点编号 x 为根的子树包含逆序对个数

显然,我们要知道子树中编号小于 x 的节点数量 ax,那么转移就很显然了:

f[x]=ax+yson(x)f[y]


考虑第二步,设 g[x] 表示以 x 为根节点的树的逆序对个数

思考由 x 换根为 y 后,g[x] 通过怎样的转换关系得到 g[y]

image

这里还需要增加一个 bx 表示以 x 为根的子树内编号小于 fa[x] 的节点数量

需要注意的是,总的小于 y 的节点个数是 y1,已知 y 子树内的数量有 ay,相减即子树外的部分

g[y]=g[x]by+(y1ay)


对于 axay 的求解

也是很重要的一部分,这里是很经典的建值域线段树,或是树状数组,只不过这题是在树上

要求子树 x 内的值,可以作差一下,用全局的值减掉子树外的值即可,对应的就是递归完子树和递归子树前

时间是 O(nlogn)

#include <bits/stdc++.h>
#define re register int
#define lp p << 1
#define rp p << 1 | 1
#define int long long
using namespace std;
const int N = 2e5 + 10;
struct Edge
{
int to, next;
}e[N << 1];
int idx, h[N];
struct Tree
{
int l, r, sum;
}t[N << 2];
int n, f[N], g[N], a[N], b[N];
inline void add(int x, int y)
{
e[++ idx] = (Edge){y, h[x]};
h[x] = idx;
}
inline void build(int p, int l, int r)
{
t[p].l = l, t[p].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
build(lp, l, mid);
build(rp, mid + 1, r);
}
inline void update(int p, int x, int k)
{
if (t[p].l == x && t[p].r == x)
{
t[p].sum += k;
return;
}
int mid = (t[p].l + t[p].r) >> 1;
if (x <= mid) update(lp, x, k);
if (x > mid) update(rp, x, k);
t[p].sum = t[lp].sum + t[rp].sum;
}
inline int query(int p, int l, int r)
{
if (l > r) return 0;
if (l <= t[p].l && t[p].r <= r) return t[p].sum;
int res = 0;
int mid = (t[p].l + t[p].r) >> 1;
if (l <= mid) res += query(lp, l, r);
if (r > mid) res += query(rp, l, r);
return res;
}
void dfs1(int u, int fa)
{
a[u] = -query(1, 1, u - 1);
b[u] = -query(1, 1, fa - 1);
for (re i = h[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == fa) continue;
dfs1(v, u);
}
update(1, u, 1);
a[u] += query(1, 1, u - 1);
b[u] += query(1, 1, fa - 1);
f[u] = a[u];
for (re i = h[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == fa) continue;
f[u] += f[v];
}
}
void dfs2(int u, int fa)
{
if (u != 1)
g[u] = g[fa] - b[u] + (u - 1 - a[u]);
for (re i = h[u]; i; i = e[i].next)
{
int v = e[i].to;
if (v == fa) continue;
dfs2(v, u);
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for (re i = 1; i < n; i ++)
{
int x, y; cin >> x >> y;
add(x, y), add(y, x);
}
build(1, 1, n);
dfs1(1, 0);
g[1] = f[1];
dfs2(1, 0);
for (re i = 1; i <= n; i ++) cout << g[i] << ' ';
cout << '\n';
return 0;
}
posted @   Zhang_Wenjie  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示