题目传送门

https://loj.ac/problem/3046

题解

首先问题就是问有多少条路径是给定的几条路径中的一条的一个子段。


先考虑链的做法。

枚举右端点 \(i\),那么求出 \(j\) 表示经过 \(i\) 的路径,左端点最小是 \(j\),那么右端点 \(i\) 的贡献就是 \(i-j+1\)。

至于求出 \(j\) 可以用直接线性地从右向左扫一遍,在右端点处枚举路径就可以了。


那么问题回到树上。

我们考虑也枚举最终的路径的一个端点。

那么,这个端点的贡献,应该就是经过这个端点的路径的并的长度。所以如果把这个端点看做根的话,那么贡献就是经过这个端点的树链的并。

根据之前做过的 bzoj3991 [SDOI2015] 寻宝游戏 的经验,树链的并的长度的二倍等于按照 dfs 序排序以后,相邻的两个点的距离的和,加上第一个点到最后一个点的距离。

那么,我们只需要能够很快地求出经过一个点 \(x\) 的路径的端点的集合,就可以通过数据结构维护出 \(x\) 的贡献了。


如何计算经过 \(x\) 的路径的端点的集合呢?

很简单,可以使用树上差分,对于路径 \(x \longleftrightarrow lca \longleftrightarrow y\),在 \(x\) 的集合中放上 \(x, y\) 两个点,在 \(y\) 的集合中放上 \(x, y\) 两个点,最后在 \(fa[lca]\) 中删去 \(x, y\)。然后使用线段树合并可以把集合递交给父节点。


感受:ZJOI 竟然有签到题。


如果使用 RMQ 求解 LCA,那么时间复杂度 \(O(n\log n)\)。

  1. #include<bits/stdc++.h>
  2. #define fec(i, x, y) (int i = head[x], y = g[i].to; i; i = g[i].ne, y = g[i].to)
  3. #define dbg(...) fprintf(stderr, __VA_ARGS__)
  4. #define File(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
  5. #define fi first
  6. #define se second
  7. #define pb push_back
  8. template<typename A, typename B> inline char smax(A &a, const B &b) {return a < b ? a = b , 1 : 0;}
  9. template<typename A, typename B> inline char smin(A &a, const B &b) {return b < a ? a = b , 1 : 0;}
  10. typedef long long ll; typedef unsigned long long ull; typedef std::pair<int, int> pii;
  11. template<typename I> inline void read(I &x) {
  12. int f = 0, c;
  13. while (!isdigit(c = getchar())) c == '-' ? f = 1 : 0;
  14. x = c & 15;
  15. while (isdigit(c = getchar())) x = (x << 1) + (x << 3) + (c & 15);
  16. f ? x = -x : 0;
  17. }
  18. const int N = 1e5 + 7;
  19. const int LOG = 18;
  20. int n, m, dfc, dfc2, nod;
  21. ll ans;
  22. int f[N], dfn[N], pre[N], seq[N << 1], dfn2[N], lc[N << 1][LOG], dep[N];
  23. int rt[N];
  24. struct Edge { int to, ne; } g[N << 1]; int head[N], tot;
  25. inline void addedge(int x, int y) { g[++tot].to = y, g[tot].ne = head[x], head[x] = tot; }
  26. inline void adde(int x, int y) { addedge(x, y), addedge(y, x); }
  27. inline void dfs1(int x, int fa = 0) {
  28. f[x] = fa, dfn[x] = ++dfc, dfn2[x] = ++dfc2, pre[dfc] = seq[dfc2] = x, dep[x] = dep[fa] + 1;
  29. for fec(i, x, y) if (y != fa) dfs1(y, x), seq[++dfc2] = x;
  30. }
  31. inline void rmq_init() {
  32. for (int i = 1; i <= dfc2; ++i) lc[i][0] = seq[i];
  33. for (int j = 1; (1 << j) <= dfc2; ++j)
  34. for (int i = 1; i + (1 << j) - 1 <= dfc2; ++i) {
  35. int a = lc[i][j - 1], b = lc[i + (1 << (j - 1))][j - 1];
  36. lc[i][j] = dep[a] < dep[b] ? a : b;
  37. }
  38. }
  39. inline int qmin(int l, int r) {
  40. int k = std::__lg(r - l + 1), a = lc[l][k], b = lc[r - (1 << k) + 1][k];
  41. return dep[a] < dep[b] ? a : b;
  42. }
  43. inline int lca(int x, int y) { return dfn2[x] < dfn2[y] ? qmin(dfn2[x], dfn2[y]) : qmin(dfn2[y], dfn2[x]); }
  44. inline int dist(int x, int y) { return dep[x] + dep[y] - (dep[lca(x, y)] << 1); }
  45. struct Node { int lc, rc, val, s, ls, rs; } t[N * 120];
  46. inline void pushup(int o) {
  47. if (t[t[o].lc].ls) t[o].ls = t[t[o].lc].ls; else t[o].ls = t[t[o].rc].ls;
  48. if (t[t[o].rc].rs) t[o].rs = t[t[o].rc].rs; else t[o].rs = t[t[o].lc].rs;
  49. t[o].val = t[t[o].lc].val + t[t[o].rc].val;
  50. if (t[t[o].lc].rs && t[t[o].rc].ls) t[o].val += dist(t[t[o].lc].rs, t[t[o].rc].ls);
  51. t[o].s = t[t[o].lc].s + t[t[o].rc].s;
  52. // dbg("o = %d, t[o].lc = %d, t[o].rc = %d, t[o].ls = %d, t[o].rs = %d, t[o].val = %d, t[o].s = %d\n", o, t[o].lc, t[o].rc, t[o].ls, t[o].rs, t[o].val, t[o].s);
  53. assert((!!t[o].ls) == (!!t[o].rs));
  54. if (t[o].ls) assert(!((t[o].val + dist(t[o].ls, t[o].rs)) & 1));
  55. // assert((!!t[o].s) == (!!t[o].ls));
  56. }
  57. inline void ins(int &o, int L, int R, int x, int k) {
  58. if (!o) o = ++nod;
  59. t[o].s += k;
  60. if (L == R) return (void)(t[o].ls = t[o].rs = t[o].s ? pre[L] : 0);
  61. int M = (L + R) >> 1;
  62. if (x <= M) ins(t[o].lc, L, M, x, k);
  63. else ins(t[o].rc, M + 1, R, x, k);
  64. pushup(o);
  65. }
  66. inline int merge(int o, int p) {
  67. if (!o || !p) return o ^ p;
  68. t[o].lc = merge(t[o].lc, t[p].lc);
  69. t[o].rc = merge(t[o].rc, t[p].rc);
  70. if (t[o].lc || t[o].rc) pushup(o);
  71. else t[o].s = t[o].s + t[p].s, t[o].ls = t[o].rs = t[o].s ? t[o].ls | t[p].ls : 0;
  72. return o;
  73. }
  74. inline void debug(int o, int L, int R) {
  75. // dbg("o = %d, L = %d, R = %d, t[o].lc = %d, t[o].rc = %d, t[o].ls = %d, t[o].rs = %d, t[o].val = %d, t[o].s = %d\n", o, L, R, t[o].lc, t[o].rc, t[o].ls, t[o].rs, t[o].val, t[o].s);
  76. assert(t[o].s >= 0);
  77. assert((!!t[o].s) == !!(t[o].ls));
  78. if (L == R) return;
  79. int M = (L + R) >> 1;
  80. debug(t[o].lc, L, M);
  81. debug(t[o].rc, M + 1, R);
  82. }
  83. inline void dfs2(int x, int fa = 0) {
  84. for fec(i, x, y) if (y != fa) dfs2(y, x), rt[x] = merge(rt[x], rt[y]);
  85. ans += (t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) / 2;
  86. // dbg("****** x = %d, ls = %d, rs = %d, dif = %d, %d, %d\n", x, t[rt[x]].ls, t[rt[x]].rs, (t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) / 2, t[rt[x]].val, dist(t[rt[x]].ls, t[rt[x]].rs));
  87. // debug(rt[x], 1, n);
  88. assert(!((t[rt[x]].val + dist(t[rt[x]].ls, t[rt[x]].rs)) & 1));
  89. }
  90. inline void work() {
  91. dfs2(1);
  92. printf("%lld\n", ans / 2);
  93. }
  94. inline void init() {
  95. read(n), read(m);
  96. int x, y;
  97. for (int i = 1; i < n; ++i) read(x), read(y), adde(x, y);
  98. dfs1(1), rmq_init();
  99. // for (int i = 1; i <= n; ++i) dbg("i = %d, dfn[i] = %d, dfn2[i] = %d\n", i, dfn[i], dfn2[i]);
  100. for (int i = 1; i <= m; ++i) {
  101. int x, y, p;
  102. read(x), read(y);
  103. p = lca(x, y);
  104. // dbg("x = %d, y = %d, p = %d\n", x, y, p);
  105. ins(rt[x], 1, n, dfn[y], 1), ins(rt[x], 1, n, dfn[x], 1);
  106. ins(rt[y], 1, n, dfn[x], 1), ins(rt[y], 1, n, dfn[y], 1);
  107. if (f[p]) ins(rt[f[p]], 1, n, dfn[x], -2), ins(rt[f[p]], 1, n, dfn[y], -2);
  108. }
  109. // dbg("****************** %d\n", lc[1][1]);
  110. }
  111. int main() {
  112. #ifdef hzhkk
  113. freopen("hkk.in", "r", stdin);
  114. #endif
  115. init();
  116. work();
  117. fclose(stdin), fclose(stdout);
  118. return 0;
  119. }

bzoj5518 & loj3046 「ZJOI2019」语言 线段树合并+树链的并的更多相关文章

  1. 【LOJ】#3046. 「ZJOI2019」语言

    LOJ#3046. 「ZJOI2019」语言 先orz zsy吧 有一个\(n\log^3n\)的做法是把树链剖分后,形成logn个区间,这些区间两两搭配可以获得一个矩形,求矩形面积并 然后就是对于一 ...

  2. 「ZJOI2019」语言 解题报告

    「ZJOI2019」语言 3个\(\log\)做法比较简单,但是写起来还是有点麻烦的. 大概就是树剖把链划分为\(\log\)段,然后任意两段可以组成一个矩形,就是个矩形面积并,听说卡卡就过去了. 好 ...

  3. @loj - 3046@「ZJOI2019」语言

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 九条可怜是一个喜欢规律的女孩子.按照规律,第二题应该是一道和数据 ...

  4. 「ZJOI2019」语言

    传送门 Description 给定一棵\(n\)个点的树和\(m\)条链,两个点可以联会当且仅当它们同在某一条链上,求可以联会的点的方案数 \(n,m\leq10^5\) Solution  考虑计 ...

  5. 【线段树 树链剖分 差分 经典技巧】loj#3046. 「ZJOI2019」语言【未完】

    还是来致敬一下那过往吧 题目分析 先丢代码 #include<bits/stdc++.h> ; ; ; struct node { int top,son,fa,tot; }a[maxn] ...

  6. 【LOJ】#3043. 「ZJOI2019」线段树

    LOJ#3043. 「ZJOI2019」线段树 计数转期望的一道好题-- 每个点设两个变量\(p,q\)表示这个点有\(p\)的概率有标记,有\(q\)的概率到祖先的路径上有个标记 被覆盖的点$0.5 ...

  7. 「ZJOI2019」线段树 解题报告

    「ZJOI2019」线段树 听说有人喷这个题简单,然后我就跑去做,然后自闭感++,rp++(雾) 理性分析一波,可以发现最后形成的\(2^k\)个线段树,对应的操作的一个子集,按时间顺序作用到这颗线段 ...

  8. loj#2255. 「SNOI2017」炸弹 线段树优化建图,拓扑,缩点

    loj#2255. 「SNOI2017」炸弹 线段树优化建图,拓扑,缩点 链接 loj 思路 用交错关系建出图来,发现可以直接缩点,拓扑统计. 完了吗,不,瓶颈在于边数太多了,线段树优化建图. 细节 ...

  9. 「ZJOI2019」&「十二省联考 2019」题解索引

    「ZJOI2019」&「十二省联考 2019」题解索引 「ZJOI2019」 「ZJOI2019」线段树 「ZJOI2019」Minimax 搜索 「十二省联考 2019」 「十二省联考 20 ...

随机推荐

  1. What does the dot after dollar sign mean in jQuery when declaring variables?

    https://stackoverflow.com/questions/22156664/what-does-the-dot-after-dollar-sign-mean-in-jquery-when ...

  2. 查看Oracle数据库中的执行计划

    1.set autotrace traceonly命令 2.explain plan for命令 1)explain plan for select * from dual; 2)select * f ...

  3. ES6模块与CommonJS模块的差异

    ES6模块与CommonJS模块的差异 讨论 Node 加载 ES6模块之前,必须了解 ES6模块与 CommonJS模块完全不同. 它们有两个重大差异. CommonJS模块输出的是一个值的拷贝,E ...

  4. 004-unity3d MonoBehaviour脚本方法简介

    一.MonoBehaviour 1.公共方法 CancelInvoke Cancels all Invoke calls on this MonoBehaviour. Invoke Invokes t ...

  5. Navicat Premium for Mac 非官方版不能启动的解决方案

    Ps:这篇有点杂记的感觉,就说点废话也没什么影响.废话主要有两点: 1.建议读者也开始写博客,为什么呢?其实我也没有这种写作的习惯,我最开始写博客的时候,感觉我写的东西网上都有,需要的时候找一下肯定能 ...

  6. DataFrame 结构

    概念 DataFrame 是表格型的数据结构 ,DataFrame 本质上可以看做是由series 组成的字典, 它既有行索引,也有列索引.  它并不是列表,也不是字典,.

  7. 40 insert语句的锁

    40 insert语句的锁 上一篇文章中对mysql自增主键锁做了优化,尽量在申请到自增id后,就释放自增锁. 因此,insert语句是一个很轻量的操作,不过,这个结论对于”普通的insert”才生效 ...

  8. 前端借助接口获取ip地址

    <script language="javascript" src="http://www.codefans.net/ajaxjs/jquery1.3.2.js&q ...

  9. spring包

    下载的spring包中文件及各种包众多,在项目中往往只有部分是我们必须的,如果不清楚什么时候需要什么包的话,看看下面就知道了. aspectj目录 下是在Spring框架下使用aspectj的源代码和 ...

  10. LeetCode 算法 Part 1

    目录 1. 两数之和 1. 题目 2.代码 4. 算法用时 5. 感想 2. 两数相加 1. 题目 2.代码 4. 算法用时 5. 感想 3. 无重复字符的最长子串 1. 题目 2.代码 4. 算法用 ...