另一道树题

题目大意

数据范围


题解

这个题第一眼能发现的是,我们的答案分成两种情况。

第一种是在非根节点汇合,第二种是在根节点汇合。

尝试枚举在第几回合结束,假设在第$i$回合结束的方案数为$f_i$,那么总答案就是$\sum\limits_{i = 1} ^ {N - 1}i\times f_i$。

显然没法求这个$f_i$....

进而,觉得这鬼东西的后缀和好像比较好求,就是$g _ i = \sum\limits_{j = i} ^ {N - 1} f _ j$。

由于我们就相当于对于深度相等的点的讨论,不难想到$bfs$序。

只考虑不在根节点汇合的情况。

发现,其实就是一段连续的区间,他们在$i$不小于一个值的时候,最多只能选取一个值。

也就是说随着我们枚举的回合数递增,这些连续的区间会存在一些合并的情况。

至于什么时候合并呢?其实就根据,相邻两个点到其$lca$的深度有关(这两个点的深度得相等),就是在这个深度差恰好等于回合数的时候,我们实施合并操作。

这样就完美的解决了不是非根汇合的情况。

考虑在根节点汇合咋办。

其实就相当于,随着回合数递增,所有深度不大于$i$的点只能选一个,就相当于和根节点合并咯。

总之通通用并查集维护就好了。

代码

#include <bits/stdc++.h>

#define N 200010 

using namespace std;

int head[N], to[N << 1], nxt[N << 1], tot;

struct Node {
int x, y;
}; vector <Node> v[N]; queue <int> q; int f[20][N], g[N], F[N], S[N], dep[N], dic[N], n, inv[N]; const int mod = 998244353 ; typedef long long ll; char *p1, *p2, buf[100000]; #define nc() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++ ) int rd() {
int x = 0, f = 1;
char c = nc();
while (c < 48) {
if (c == '-')
f = -1;
c = nc();
}
while (c > 47) {
x = (((x << 2) + x) << 1) + (c ^ 48), c = nc();
}
return x * f;
} int qpow(int x, int y) {
int ans = 1;
while (y) {
if (y & 1) {
ans = (ll)ans * x % mod;
}
y >>= 1;
x = (ll)x * x % mod;
}
return ans;
} inline void add(int x, int y) {
to[ ++ tot] = y;
nxt[tot] = head[x];
head[x] = tot;
} int lca(int x, int y) {
if (dep[x] < dep[y])
swap(x, y);
for (int i = 19; ~i; i -- ) {
if (dep[f[i][x]] >= dep[y]) {
x = f[i][x];
}
}
if (x == y)
return x;
for (int i = 19; ~i; i -- ) {
if (f[i][x] != f[i][y]) {
x = f[i][x];
y = f[i][y];
}
}
return f[0][x];
} void dfs(int p, int fa) {
v[dep[p]].push_back((Node){1, p});
f[0][p] = fa;
for (int i = 1; i <= 19; i ++ ) {
f[i][p] = f[i - 1][f[i - 1][p]];
}
for (int i = head[p]; i; i = nxt[i]) {
if (to[i] != fa) {
dep[to[i]] = dep[p] + 1;
dfs(to[i], p);
}
}
} void bfs() {
while (!q.empty())
q.pop();
q.push(1);
int cnt = 0;
while (!q.empty()) {
int x = q.front();
q.pop();
dic[ ++ cnt] = x;
for (int i = head[x]; i; i = nxt[i]) {
if (to[i] != f[0][x]) {
q.push(to[i]);
}
}
}
for (int i = 1; i < n; i ++ ) {
if (dep[dic[i]] == dep[dic[i + 1]]) {
v[dep[dic[i]] - dep[lca(dic[i], dic[i + 1])]].push_back((Node) {dic[i], dic[i + 1]});
}
}
} int find(int x) {
return F[x] == x ? x : F[x] = find(F[x]);
} int main() {
n = rd();
for (int i = 1; i <= n; i ++ ) {
F[i] = i;
S[i] = 1;
}
for (int i = 2; i <= n; i ++ ) {
int x = rd();
add(x, i);
add(i, x);
}
dfs(1, 1);
bfs();
inv[0] = 1;
for (int i = 1; i <= n; i ++ )
inv[i] = qpow(i, mod - 2); // for (int i = 0 ; i <= n; i ++ ) {
// printf("%d ", inv[i]);
// }
// puts(""); int mdl = qpow(2, n);
for (int i = 1; i < n; i ++ ) {
g[i] = (mdl - n - 1 + mod) % mod;
int len = v[i].size();
for (int j = 0; j < len; j ++ ) {
int x = v[i][j].x, y = v[i][j].y;
x = find(x), y = find(y);
if (x != y) {
mdl = (ll)mdl * inv[S[x] + 1] % mod * inv[S[y] + 1] % mod;
F[x] = y; S[y] += S[x];
mdl = (ll)mdl * (S[y] + 1) % mod;
}
}
}
int ans = 0;
for (int i = 1; i < n; i ++ ) {
ans = (ans + (ll)(g[i] - g[i + 1] + mod) % mod * i % mod) % mod;
}
cout << ans << endl ;
return 0;
}

小结:好题好题,这个题的思路行云流水。重点是能否想到把那个,一段区间只能选一个这个事情考虑清楚,从而转变成区间的合并问题,这是关键。

[Comet OJ - Contest #6 D][48D 2280]另一道树题_并查集的更多相关文章

  1. Comet OJ - Contest #2 简要题解

    Comet OJ - Contest #2 简要题解 cometoj A 模拟,复杂度是对数级的. code B 易知\(p\in[l,r]\),且最终的利润关于\(p\)的表达式为\(\frac{( ...

  2. Comet OJ - Contest #2简要题解

    Comet OJ - Contest #2简要题解 前言: 我没有小裙子,我太菜了. A 因自过去而至的残响起舞 https://www.cometoj.com/contest/37/problem/ ...

  3. Comet OJ - Contest #4--前缀和

    原题:Comet OJ - Contest #4-B https://www.cometoj.com/contest/39/problem/B?problem_id=1577传送门 一开始就想着暴力打 ...

  4. Comet OJ - Contest #11 题解&赛后总结

    Solution of Comet OJ - Contest #11 A.eon -Problem designed by Starria- 在模 10 意义下,答案变为最大数的最低位(即原数数位的最 ...

  5. Comet OJ - Contest #8

    Comet OJ - Contest #8 传送门 A.杀手皇后 签到. Code #include <bits/stdc++.h> using namespace std; typede ...

  6. Comet OJ - Contest #13-C2

    Comet OJ - Contest #13-C2 C2-佛御石之钵 -不碎的意志-」(困难版) 又是一道并查集.最近做过的并查集的题貌似蛮多的. 思路 首先考虑,每次处理矩形只考虑从0变成1的点.这 ...

  7. Comet OJ - Contest #13 「火鼠的皮衣 -不焦躁的内心-」

    来源:Comet OJ - Contest #13 芝士相关: 复平面在信息学奥赛中的应用[雾 其实是道 sb 题??? 发现原式貌似十分可二项式定理,然后发现确实如此 我们把 \(a^i\) 替换成 ...

  8. Comet OJ - Contest #13 「佛御石之钵 -不碎的意志-」(hard)

    来源:Comet OJ - Contest #13 一眼并查集,然后发现这题 tmd 要卡常数的说卧槽... 发现这里又要用并查集跳过访问点,又要用并查集维护联通块,于是开俩并查集分别维护就好了 一开 ...

  9. Comet OJ - Contest #5

    Comet OJ - Contest #5 总有一天,我会拿掉给\(dyj\)的小裙子的. A 显然 \(ans = min(cnt_1/3,cnt_4/2,cnt5)\) B 我们可以感性理解一下, ...

随机推荐

  1. luogu 1156 垃圾陷阱 动态规划

    Code: #include <bits/stdc++.h> #define N 4004 #define setIO(s) freopen(s".in"," ...

  2. js 获取当前focus 的 input 元素

    document.querySelector("#pro-code").focus(); console.log("focus:" + document.act ...

  3. Codevs 2482 宝库通道 2007年省队选拔赛安徽

    2482 宝库通道 2007年省队选拔赛安徽 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 大师 Master 题目描述 Description 探宝的旅程仍然继续中,由于你的帮助 ...

  4. Depth from Videos in the Wild 解读

    2019年7月17日11:37:05 论文 Depth from Videos in the Wild: Unsupervised Monocular Depth Learning from Unkn ...

  5. c 判断一个字符是否为字母

    #include <stdio.h> #include <wctype.h> int main () { ; wchar_t str[] = L"C++"; ...

  6. 阿里云修改主机名hostname

    一.永久修改主机名的方法(针对于普通的服务器) 1.通过hostname命令修改. [root@izwz9f7pm0tw36neb1j7gmz ~]# hostname node1 修改完之后发现主机 ...

  7. CodeForces 755D PolandBall and Polygon ——(xjbg)

    每次连线,起点和终点之间,每一个被点亮的点,这些点都能连出去两条线,因此可以增加的块数+2(1这个点除外,因为只有连出的点没有连进的点),计算起点和终点之间有几个点被点亮即可,然后1这个点特判一下.感 ...

  8. springMVC课程笔记(二)springMVC组件配置

    1.springMVC的DispatcherServlet前段控制器配置,如下图所示在web.xml中配置如下内容: 2.在spring配置文件中,配置处理器适配器HandlerAdapter和映射器 ...

  9. hibernate持久化类中,修改字符串长度时,注意的问题

    在使用hibernate注解修饰字符串长度时,如果一开始没有把String类型的变量长度设计好,在网数据库插入数据时,容易造成字段长度超出错误,这时候需要修改@Column里length的大小.如果使 ...

  10. js中的bind方法的实现方法

    js中目前我遇见的改变作用域的5中方法:call, apply, eval, with, bind. var obj = { color: 'green' } function demo () { c ...