P3995 树链剖分

原题传送门

适合树剖新手做的题,代码简单,思路稍微有点难理解,便于新手提升水平。

题目大意

给定一棵树,请你给出一种 剖分方案,使所有询问操作总共访问的轻重链总条数最小,由于可能有许多合法方案,请任意输出一种。

解题思路

前置知识: 倍增求最近公共祖先,树上差分,对树剖的理解。(好像跟树剖没什么大关系)

预处理:

  • dfs 预处理出深度和父亲。
  • 倍增预处理。

回顾往事,常规树剖的重儿子都是取决于子树大小,所以 s i z e size size 的定义是以 u u u 为根节点的子树大小。

分析样例,可以得出访问的轻重链总条数取决于点跳到链头的次数。

所以这道题就是让每一个节点的重儿子被访问的次数最多,即 s i z e size size 应定义为一个节点的被访问次数。

注意这时的重儿子判断应该是:

if (siz[v] >= siz[hson[u]])
	hson[u] = v;

因为原先的 s i z e size size 最小为 1 1 1,所以我们只需用小于号就行了。但这题不一样, s i z e size size 有可能为 0 0 0,所以我们要用小于等于号才行。

详细一点,就是因为如果结点 u u u 只有一个儿子,然后这个儿子子树里没有询问,那 s i z e size size 就一直为 0 0 0 了,但是 u u u 显然不是叶子节点。

对于一条询问路径,可以发现一个点的贡献会在 l c a lca lca 处消失,所以可以使用树上差分。

每读入一组询问,就把 s i z e [ a ] size[a] size[a] s i z e [ b ] size[b] size[b] 1 1 1,而因为树链刨分的时候只会访问到 l c a ( a , b ) lca(a,b) lca(a,b)的下一层 ,我们就把 s i z [ l c a ( a , b ) ] siz[lca(a,b)] siz[lca(a,b)] 2 2 2,有一点类似边差分。

但是其实会有问题:

f a [ a ] = l c a fa[a]=lca fa[a]=lca f a [ b ] = l c a fa[b]=lca fa[b]=lca 时,无论如何都要被操作一次,对我们来说不会有任何贡献,我们要让可以调整的查询尽量多的落在重儿子上。

那么剩下的就是直接用新的规则来进行树剖即可。

AC CODE

由于笔者快读代码太长,不适合放在这里,请自行加入快读。

#include <bits/stdc++.h>
#define _ 1000002
#define lson o << 1
#define rson o << 1 | 1
using namespace std;

int n, m;

int cnt;
array<int, _> head;
struct Edge
{
   int to, nxt;
};
array<Edge, _ << 1> e;

array<int, _> siz, dep, hson;
array<array<int, 22>, _> fa; //等价于 int fa[_][22];

inline void add(int u, int v)
{
   e[++cnt].to = v;
   e[cnt].nxt = head[u];
   head[u] = cnt;
}

inline void dfs1(int u, int d = 1)
{
   dep[u] = d;
   for (int i = head[u]; i; i = e[i].nxt)
   {
       int v = e[i].to;
       if (dep[v])
           continue;
       fa[v][0] = u;
       dfs1(v, d + 1);
   }
}

inline void dfs2(int u)
{
   for (int i = head[u]; i; i = e[i].nxt)
   {
       int v = e[i].to;
       if (v == fa[u][0])
           continue;
       dfs2(v);
       siz[u] += siz[v];
       if (siz[v] > siz[hson[u]])
           hson[u] = v;
   }
}

int LCA(int u, int v)
{
   if (dep[u] > dep[v])
       swap(u, v);
   for (int i = 20; i >= 0; --i)
       if (dep[fa[v][i]] >= dep[u])
           v = fa[v][i];
   if (u == v)
       return u;
   for (int i = 20; i >= 0; --i)
       if (fa[u][i] != fa[v][i])
       {
           u = fa[u][i];
           v = fa[v][i];
       }
   return fa[u][0];
}

signed main()
{
   cin >> n >> m;
   for (int i = 1; i < n; ++i)
   {
       int a, b;
       cin >> a >> b;
       add(a, b);
       add(b, a);
   }
   dfs1(1);
   for (int j = 1; j <= 20; ++j)
       for (int i = 1; i <= n; ++i)
           fa[i][j] = fa[fa[i][j - 1]][j - 1];
   for (int i = 1; i <= m; ++i)
   {
       int a, b;
       cin >> a >> b;
       int lca = LCA(a, b);
       if (fa[a][0] != lca)
       {
           siz[a]++;
           siz[lca]--;
       }
       if (fa[b][0] != lca)
       {
           siz[b]++;
           siz[lca]--;
       }
   }
   dfs2(1);
   for (int i = 1; i <= n; ++i)
       cout << hson[i] << endl;
   return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值