@description@

小 Y 家里有一个大森林,里面有 n 棵树,编号从 1 到 n。一开始这些树都只是树苗,只有一个节点,标号为 1。这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力。小 Y 掌握了一种魔法,能让第 l 棵树到第 r 棵树的生长节点长出一个子节点。同时她还能修改第 l 棵树到第 r 棵树的生长节点。她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?

输入格式

第一行包含两个正整数 ,表示共有 n 棵树和 m 个操作。

接下来 m 行,每行包含若干非负整数表示一个操作,操作格式为:

0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例如,第一个 0 号操作产生的子节点标号为 2), l 到 r 之间的树长出的节点标号都相同。保证 1<=l<=r<=n 。

1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (l<=i<=r) 这棵树,如果标号 x 的点不在其中,那么这个操作对该树不产生影响。保证 1<=l<=r<=n , x 不超过当前所有树中节点最大的标号。

2 x u v 询问第 x 棵树中节点 u 到节点 v 点的距离,也就是在第 x 棵树中从节点 u 和节点 v 的最短路上边的数量。保证 1<=x<=n,这棵树中节点 u 和节点 v 存在。

输出格式

输出包括若干行,按顺序对于每个小 Y 的询问输出答案

样例输入

5 5

0 1 5

1 2 4 2

0 1 4

2 1 1 3

2 2 1 3

样例输出

1

2

数据范围与提示

N <= 10^5, M <= 2*10^5。

@solution@

对于每个 1 操作 l r x,我们发现它能影响到的树就是含有点 x 的树。而含有点 x 的树对应了一个 0 操作 l' r'。

则它能产生影响的树实际上只有 [l, r] 与 [l', r'] 的交集,可以发现这个也是一个区间。

因此,我们可以把 1 操作变成 “将某个区间内所有树的生长结点改成 x”,并且保证该区间都存在 x 这个结点。

然后我们再来看 0 操作。因为 1 操作(变化过后的)与 2 操作都保证了结点的存在性,那么假如我们加入一些不存在的结点,也不会影响答案。

因此 0 操作给定的区间 [l, r] 就没有什么用了。我们就可以视作所有树都长了这样一个结点出来。

对于 2 操作,我们的解决方法是:先维护出这棵树长什么样子,然后查询。

我们可以将 n 棵树看成 1 棵树的 n 个时刻,则每个 1 操作相当于在第 l 个时刻初插入一个操作,在第 r 个时刻末删除一个操作。

然后,对于每个 1 操作我们都建立 1 个虚点,第 i 个虚点连向第 i 次 1 操作到第 i+1 次 1 操作之间的 0 操作加入的实点(因为这些点在任意时刻都是在同一个父亲下的)。

假如第 i 个虚点连向第 j 个虚点,则我们理解为第 i 个虚点所管的实点与第 j 个虚点所管的实点在同一个父亲下。

假如第 i 个虚点连向一个实点 x,则我们理解为第 i 个虚点所管的实点的父亲为 x。

初始时所有操作都未执行,那么每个 1 操作对应的虚点向前一个虚点连边;第一个虚点连向结点 1。

每次插入一个 1 操作,就把这个 1 操作对应的虚点 i 连向实点 x;每次删除时,把这个 1 操作对应的虚点 i 连回第 i-1 个虚点。

查询不能直接查询两点之间的路径上的实点数量,需要对 lca 是实点还是虚点进行分类。

不过有一个替代方案:记 f(x) 表示 x 到根的实点数量,则直接查 f(u) + f(v) - 2*f( lca(u, v) ) 即可。

至于为什么,还是需要对 lca 是实点还是虚点进行分类。

@accepted code@

  1. #include<vector>
  2. #include<cstdio>
  3. #include<iostream>
  4. #include<algorithm>
  5. using namespace std;
  6. const int MAXN = 100000;
  7. const int MAXM = 200000;
  8. struct link_cut_tree{
  9. struct node{
  10. int rev;
  11. int siz, key;
  12. node *ch[2], *fa;
  13. }pl[MAXM + 5], *ncnt, *NIL;
  14. link_cut_tree() {
  15. ncnt = NIL = &pl[0];
  16. NIL->fa = NIL->ch[0] = NIL->ch[1] = NIL;
  17. NIL->siz = NIL->key = 0;
  18. }
  19. void maintain(node *x) {
  20. if( x != NIL ) {
  21. swap(x->ch[0], x->ch[1]);
  22. x->rev ^= 1;
  23. }
  24. }
  25. void pushup(node *x) {x->siz = x->ch[0]->siz + x->ch[1]->siz + x->key;}
  26. void pushdown(node *x) {
  27. if( x->rev )
  28. maintain(x->ch[0]), maintain(x->ch[1]), x->rev = 0;
  29. }
  30. node *newnode(int k) {
  31. node *p = (++ncnt);
  32. p->fa = p->ch[0] = p->ch[1] = NIL;
  33. p->siz = p->key = k;
  34. return p;
  35. }
  36. bool is_root(node *x) {
  37. return x->fa->ch[0] != x && x->fa->ch[1] != x;
  38. }
  39. void set_child(node *x, node *y, int d) {
  40. if( x != NIL ) x->ch[d] = y;
  41. if( y != NIL ) y->fa = x;
  42. }
  43. void rotate(node *x) {
  44. node *y = x->fa; int d = (y->ch[1] == x);
  45. pushdown(y), pushdown(x);
  46. if( is_root(y) ) x->fa = y->fa;
  47. else set_child(y->fa, x, y->fa->ch[1] == y);
  48. set_child(y, x->ch[!d], d);
  49. set_child(x, y, !d);
  50. pushup(y);
  51. }
  52. void splay(node *x) {
  53. pushdown(x);
  54. while( !is_root(x) ) {
  55. node *y = x->fa;
  56. if( is_root(y) )
  57. rotate(x);
  58. else {
  59. node *z = y->fa;
  60. if( (z->ch[1] == y) == (y->ch[1] == x) )
  61. rotate(y);
  62. else rotate(x);
  63. rotate(x);
  64. }
  65. }
  66. pushup(x);
  67. }
  68. void access(node *x) {
  69. node *y = NIL;
  70. while( x != NIL ) {
  71. splay(x);
  72. x->ch[1] = y;
  73. pushup(x);
  74. y = x, x = x->fa;
  75. }
  76. }
  77. void make_root(node *x) {
  78. access(x), splay(x), maintain(x);
  79. }
  80. void link(node *x, node *y) {
  81. make_root(x), x->fa = y;
  82. }
  83. void cut(node *x, node *y) {
  84. make_root(x), access(y), splay(y);
  85. x->fa = NIL, y->ch[0] = y->ch[1] = NIL;
  86. pushup(y);
  87. }
  88. node *lca(node *x, node *y) {
  89. access(x), access(y), splay(x);
  90. if( x->fa == NIL ) return x;
  91. else return x->fa;
  92. }
  93. int query(node *x, node *y) {
  94. access(x), splay(x); int p = x->siz;
  95. access(y), splay(y); int q = y->siz;
  96. node *l = lca(x, y);
  97. access(l), splay(l); int r = l->siz;
  98. return p + q - 2*r;
  99. }
  100. }T;
  101. struct modify{link_cut_tree::node *a, *b, *c;}tmp;
  102. struct node{int id, u, v;}tmp2;
  103. vector<modify>mdf[MAXM + 5];
  104. vector<node>qry[MAXM + 5];
  105. link_cut_tree::node *nd0[MAXM + 5], *nd1[MAXM + 5];
  106. int le[MAXM + 5], ri[MAXM + 5];
  107. int cnt0, cnt1, cnt2;
  108. int ans[MAXM + 5];
  109. int main() {
  110. int n, m; scanf("%d%d", &n, &m);
  111. nd0[cnt0 = 1] = nd1[cnt1 = 1] = T.newnode(0);
  112. le[1] = 1, ri[1] = n;
  113. for(int i=1;i<=m;i++) {
  114. int op; scanf("%d", &op);
  115. if( op == 0 ) {
  116. cnt0++; scanf("%d%d", &le[cnt0], &ri[cnt0]);
  117. nd0[cnt0] = T.newnode(1);
  118. T.link(nd0[cnt0], nd1[cnt1]);
  119. }
  120. else if( op == 1 ) {
  121. int l, r, x; scanf("%d%d%d", &l, &r, &x);
  122. l = max(l, le[x]), r = min(r, ri[x]);
  123. if( l > r ) continue;
  124. nd1[++cnt1] = T.newnode(0);
  125. tmp.a = nd1[cnt1], tmp.b = nd1[cnt1-1], tmp.c = nd0[x], mdf[l].push_back(tmp);
  126. tmp.a = nd1[cnt1], tmp.b = nd0[x], tmp.c = nd1[cnt1-1], mdf[r + 1].push_back(tmp);
  127. T.link(nd1[cnt1], nd1[cnt1-1]);
  128. }
  129. else {
  130. int x, u, v; scanf("%d%d%d", &x, &u, &v);
  131. tmp2.id = (++cnt2), tmp2.u = u, tmp2.v = v, qry[x].push_back(tmp2);
  132. }
  133. }
  134. for(int i=1;i<=n;i++) {
  135. for(int j=0;j<mdf[i].size();j++)
  136. T.cut(mdf[i][j].a, mdf[i][j].b), T.link(mdf[i][j].a, mdf[i][j].c);
  137. for(int j=0;j<qry[i].size();j++)
  138. T.make_root(nd0[1]), ans[qry[i][j].id] = T.query(nd0[qry[i][j].u], nd0[qry[i][j].v]);
  139. }
  140. for(int i=1;i<=cnt2;i++)
  141. printf("%d\n", ans[i]);
  142. }

@details@

ZJOI 的题都是神仙题 * 3。

实现起来倒是很顺畅,基本上都是模板。主要是理解。

@loj - 2092@ 「ZJOI2016」大森林的更多相关文章

  1. 「ZJOI2016」大森林 解题报告

    「ZJOI2016」大森林 神仙题... 很显然线段树搞不了 考虑离线操作 我们只搞一颗树,从位置1一直往后移动,然后维护它的形态试试 显然操作0,1都可以拆成差分的形式,就是加入和删除 因为保证了操 ...

  2. loj2092 「ZJOI2016」大森林

    ref不是太懂-- #include <algorithm> #include <iostream> #include <cstring> #include < ...

  3. LOJ#2230. 「BJOI2014」大融合

    LOJ#2230. 「BJOI2014」大融合 题目描述 小强要在$N$个孤立的星球上建立起一套通信系统.这套通信系统就是连接$N$个点的一个树.这个树的边是一条一条添加上去的. 在某个时刻,一条边的 ...

  4. @loj - 2090@ 「ZJOI2016」旅行者

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Y 来到了一个新的城市旅行.她发现了这个城市的布局是网格状的 ...

  5. Loj 2230. 「BJOI2014」大融合 (LCT 维护子树信息)

    链接:https://loj.ac/problem/2230 思路: 设立siz数组保存虚点信息,sum表示总信息 维护子树信息link操作和access操作需要进行一些改动 可参考博客:https: ...

  6. @loj - 2093@ 「ZJOI2016」线段树

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Yuuka 遇到了一个题目:有一个序列 a1,a2,..., ...

  7. @loj - 2091@ 「ZJOI2016」小星星

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 小 Y 是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品.她有 ...

  8. 「ZJOI2016」解题报告

    「ZJOI2016」解题报告 我大浙的省选题真是超级神仙--这套已经算是比较可做的了. 「ZJOI2016」旅行者 神仙分治题. 对于一个矩形,每次我们从最长边切开,最短边不会超过 \(\sqrt{n ...

  9. Loj #3056. 「HNOI2019」多边形

    Loj #3056. 「HNOI2019」多边形 小 R 与小 W 在玩游戏. 他们有一个边数为 \(n\) 的凸多边形,其顶点沿逆时针方向标号依次为 \(1,2,3, \ldots , n\).最开 ...

随机推荐

  1. css上下左右居中

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. for循环遍历json(附习题及答案)

    三种方法 var mapColumn = { "vdoing" : "访问深度", "_visitorNumber": "访问量& ...

  3. jQuery事件绑定的四种方法

    jQuery中提供了四种绑定事件的方法,分别是bind.live.delegate.on,对应的解除监听的函数分别是unbind.die.undelegate.off: 一.on()方法(首选方法) ...

  4. Aspose Words、Excel导出等操作

    /*Word先保存再输出-下载*/ strReportFilePath = Server.MapPath("~") + strReportFilePath; doc.Save(st ...

  5. Ubuntu 16.04 LTS安装Docker最新版

    一.安装Docker的先决条件 1.运行64位CPU构架的计算机(目前只能是x86_64和amd64),请注意,Docker目前不支持32位CPU.2.运行Linux 3.8或更高版本内核.一些老版本 ...

  6. JavaBean与Map的相互转换

    package com.bizvane.utils.tools; import java.lang.reflect.Field; import java.util.Arrays; import jav ...

  7. python3.7的celery报错TypeError: wrap_socket() got an unexpected keyword argument '_context'

    原启动方法为: 起执行任务的服务 elery worker -A celery_task -l info -P eventlet 起提交任务的服务 celery beat -A celery_task ...

  8. Python2.7用sys.stdout.write实现打印刷新

    如何能在控制台实现在一行中显示进度的信息呢,就像使用pip安装时的进度那样. 如果用print则会打印成多行,下面这个小技巧可以在一行中打印: import time import sys if __ ...

  9. 【Leetcode 滑动窗口】顺次数(1291)

    题目 我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数. 请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序).   示例 1: 输出:low = ...

  10. sas信用评分之第二步变量筛选

    sas信用评分之第二步变量筛选 今天介绍变量初步选择.这部分的内容我就只介绍information –value,我这次做的模型用的逻辑回归,后面会更新以基尼系数或者信息熵基础的筛选变量,期待我把. ...