模拟赛中遇到了这个题,当时我这个沙雕因为把一个\(y\)打成了\(x\)而爆零。回来重新写这道题,莫名其妙的拿了rank1。。。

我的解法与其他几位的题解有些不同我太蒻了。并没有选取所谓的关键点,而是用树链剖分将树上问题转化为序列的问题。


序列问题的解决

  • 现在的问题是,维护一个序列,要求查询若干个区间的并集的权值种类和\(mex\),无修改操作。
  • 如果只有一个区间,那么这个问题就可以很愉快的用主席树解决了。
  • 然而要查询的是区间并的权值种类和\(mex\),而且这两个东西都不满足区间可加性,于是可以放弃线段树的思路了。
  • 可以想到用分块解决这个问题,设每一块大小为\(B\)
  • 考虑到点权\(\leq 30000\),又要合并若干个区间,于是可以想到用\(bitset\)维护区间权值信息。
  • 设\(f[][]\)为一个\(bitset\)的二维数组,\(f[l][r]\)表示从第\(l\)块到第\(r\)块(包含\(l,r\))之间的权值集合,这个数组可以在\(O(n\frac{30000}{32})\)的时间内预处理出来。每次查询的时候,整块的信息可以在\(O(\frac{30000}{32})\)的时间内解决。
  • 剩余的部分直接暴力处理,时间复杂度为\(O(B)\)。这样单次查询可以做到\(O(\frac{30000}{32}+B)\)
  • 至于区间并,直接开一个全局变量记录答案(我的代码中用的是\(cur\)),依次处理每个区间。因为重叠的部分并不会对答案有影响。现在序列的问题已经解决了。

序列处理部分的代码

  1. int b[N]; // 记录区间上每一个位置属于哪个块
  2. int L[N], R[N]; // 每个块的左右断点
  3. bitset<W> cur; // 查询时用到的全局变量
  4. bitset<W> f[110][110]; // 预处理的 f 数组
  5. void preWork() {
  6. // 预处理
  7. for (int i = 1; i <= n; ++i) {
  8. // 先计算单个块的权值情况
  9. b[i] = (i-1) / B + 1;
  10. f[b[i]][b[i]].set(a[i]);
  11. }
  12. // 处理每个块的左右端点
  13. for (int i = 1; i <= b[n]; ++i)
  14. L[i] = R[i-1] + 1, R[i] = i * B;
  15. R[b[n]] = n; // 最后一个块的右端点要特判
  16. for (int i = 1; i < b[n]; ++i)
  17. for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
  18. f[i][j] = f[i][j-1] | f[j][j];
  19. }
  20. void queryOnBlock(int l, int r) {
  21. if (b[l] == b[r]) {
  22. // 特判左右端点在同一个块内的情况
  23. for (int i = l; i <= r; ++i) cur.set(a[i]);
  24. return;
  25. }
  26. cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
  27. for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
  28. for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
  29. }
  30. int mex(bitset<W> &s) {
  31. // 暴力求 mex
  32. for (int i = 0; i < W; ++i)
  33. if (!s.test(i)) return i;
  34. return 1e9;
  35. }

将树上问题转化为序列问题

树链剖分的板子(我这个蒟蒻写挂的部分)。。。

直接贴代码了

  1. int G[N], ed = 1, w[N]; // 树的存储
  2. struct Edge {
  3. int to, nxt;
  4. Edge() { to = nxt = 0; }
  5. Edge(int to, int nxt) : to(to), nxt(nxt) {}
  6. } e[N<<1];
  7. inline void addEdge(int x, int y) {
  8. e[++ed] = Edge(y, G[x]), G[x] = ed;
  9. e[++ed] = Edge(x, G[y]), G[y] = ed;
  10. }
  11. // 树链剖分相关
  12. int dfn[N]; // dfs 序
  13. int fa[N]; // 父结点
  14. int son[N]; // 重儿子
  15. int top[N]; // 重链顶端
  16. int size[N]; // 子数大小
  17. int dep[N]; // 深度
  18. int a[N]; // 转化的序列
  19. void dfs1(int x, int p) {
  20. size[x] = 1, fa[x] = p;
  21. for (int i = G[x]; i != 0; i = e[i].nxt) {
  22. int y = e[i].to;
  23. if (y == p) continue;
  24. dep[y] = dep[x] + 1;
  25. dfs1(y, x);
  26. size[x] += size[y];
  27. if (size[son[x]] < size[y])
  28. son[x] = y;
  29. }
  30. }
  31. void dfs2(int x, int t) {
  32. static int cur = 0;
  33. dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
  34. if (!son[x]) return;
  35. dfs2(son[x], t);
  36. for (int i = G[x]; i != 0; i = e[i].nxt) {
  37. int y = e[i].to;
  38. if (y == son[x] || y == fa[x]) continue;
  39. dfs2(y, y);
  40. }
  41. }
  42. void queryOnTree(int x, int y) {
  43. // 树上查询
  44. while (top[x] != top[y]) {
  45. if (dep[top[x]] < dep[top[y]]) swap(x, y);
  46. queryOnBlock(dfn[top[x]], dfn[x]);
  47. x = fa[top[x]];
  48. }
  49. if (dfn[x] > dfn[y]) swap(x, y);
  50. queryOnBlock(dfn[x], dfn[y]);
  51. }

总代码

  1. // 2598ms 57.76MB 无O2
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <iostream>
  5. #include <algorithm>
  6. #include <bitset>
  7. #define LL long long
  8. using namespace std;
  9. inline int getint() {
  10. int x = 0, f = 1; char c = getchar();
  11. while (!isdigit(c)) { if (c == '-') f = 0; c = getchar(); }
  12. while (isdigit(c)) { x = (x*10) + (c-'0'); c = getchar(); }
  13. return f ? x : -x;
  14. }
  15. const int N = 1e5 + 10;
  16. const int B = 1e3;
  17. const int W = 30001;
  18. int n, m, flag; // flag 判断是否需要异或
  19. int ans1, ans2, lastans; // ans1 为权值种类,ans2 为权值 mex
  20. int G[N], ed = 1, w[N]; // 树的存储
  21. struct Edge {
  22. int to, nxt;
  23. Edge() { to = nxt = 0; }
  24. Edge(int to, int nxt) : to(to), nxt(nxt) {}
  25. } e[N<<1];
  26. inline void addEdge(int x, int y) {
  27. e[++ed] = Edge(y, G[x]), G[x] = ed;
  28. e[++ed] = Edge(x, G[y]), G[y] = ed;
  29. }
  30. // 树链剖分相关
  31. int dfn[N]; // dfs 序
  32. int fa[N]; // 父结点
  33. int son[N]; // 重儿子
  34. int top[N]; // 重链顶端
  35. int size[N]; // 子数大小
  36. int dep[N]; // 深度
  37. int a[N]; // 转化的序列
  38. void dfs1(int x, int p) {
  39. size[x] = 1, fa[x] = p;
  40. for (int i = G[x]; i != 0; i = e[i].nxt) {
  41. int y = e[i].to;
  42. if (y == p) continue;
  43. dep[y] = dep[x] + 1;
  44. dfs1(y, x);
  45. size[x] += size[y];
  46. if (size[son[x]] < size[y])
  47. son[x] = y;
  48. }
  49. }
  50. void dfs2(int x, int t) {
  51. static int cur = 0;
  52. dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
  53. if (!son[x]) return;
  54. dfs2(son[x], t);
  55. for (int i = G[x]; i != 0; i = e[i].nxt) {
  56. int y = e[i].to;
  57. if (y == son[x] || y == fa[x]) continue;
  58. dfs2(y, y);
  59. }
  60. }
  61. int b[N]; // 记录区间上每一个位置属于哪个块
  62. int L[N], R[N]; // 每个块的左右断点
  63. bitset<W> cur; // 查询时用到的全局变量
  64. bitset<W> f[110][110]; // 预处理的 f 数组
  65. void preWork() {
  66. // 预处理
  67. for (int i = 1; i <= n; ++i) {
  68. // 先计算单个块的权值情况
  69. b[i] = (i-1) / B + 1;
  70. f[b[i]][b[i]].set(a[i]);
  71. }
  72. // 处理每个块的左右端点
  73. for (int i = 1; i <= b[n]; ++i)
  74. L[i] = R[i-1] + 1, R[i] = i * B;
  75. R[b[n]] = n; // 最后一个块的右端点要特判
  76. for (int i = 1; i < b[n]; ++i)
  77. for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
  78. f[i][j] = f[i][j-1] | f[j][j];
  79. }
  80. void queryOnBlock(int l, int r) {
  81. if (b[l] == b[r]) {
  82. // 特判左右端点在同一个块内的情况
  83. for (int i = l; i <= r; ++i) cur.set(a[i]);
  84. return;
  85. }
  86. cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
  87. for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
  88. for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
  89. }
  90. void queryOnTree(int x, int y) {
  91. // 树上查询
  92. while (top[x] != top[y]) {
  93. if (dep[top[x]] < dep[top[y]]) swap(x, y);
  94. queryOnBlock(dfn[top[x]], dfn[x]);
  95. x = fa[top[x]];
  96. }
  97. if (dfn[x] > dfn[y]) swap(x, y);
  98. queryOnBlock(dfn[x], dfn[y]);
  99. }
  100. int mex(bitset<W> &s) {
  101. // 暴力求 mex
  102. for (int i = 0; i < W; ++i)
  103. if (!s.test(i)) return i;
  104. return 1e9;
  105. }
  106. int main() {
  107. cin >> n >> m >> flag;
  108. for (int i = 1; i <= n; ++i) w[i] = getint();
  109. for (int i = 1; i < n; ++i) {
  110. int x = getint(), y = getint();
  111. addEdge(x, y);
  112. }
  113. dfs1(1, 0), dfs2(1, 1);
  114. preWork();
  115. for (int i = 1; i <= m; ++i) {
  116. cur.reset();
  117. int num = getint();
  118. for (int j = 1; j <= num; ++j) {
  119. int x = getint() ^ (flag*lastans);
  120. int y = getint() ^ (flag*lastans);
  121. queryOnTree(x, y);
  122. }
  123. ans1 = cur.count(), ans2 = mex(cur);
  124. lastans = ans1 + ans2;
  125. printf("%d %d\n", ans1, ans2);
  126. }
  127. return 0;
  128. }

[题解] 洛谷 P3603 雪辉的更多相关文章

  1. 题解 洛谷P5018【对称二叉树】(noip2018T4)

    \(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...

  2. 题解 洛谷 P3396 【哈希冲突】(根号分治)

    根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...

  3. 题解-洛谷P5410 【模板】扩展 KMP(Z 函数)

    题面 洛谷P5410 [模板]扩展 KMP(Z 函数) 给定两个字符串 \(a,b\),要求出两个数组:\(b\) 的 \(z\) 函数数组 \(z\).\(b\) 与 \(a\) 的每一个后缀的 L ...

  4. 题解-洛谷P4229 某位歌姬的故事

    题面 洛谷P4229 某位歌姬的故事 \(T\) 组测试数据.有 \(n\) 个音节,每个音节 \(h_i\in[1,A]\),还有 \(m\) 个限制 \((l_i,r_i,g_i)\) 表示 \( ...

  5. 题解-洛谷P4724 【模板】三维凸包

    洛谷P4724 [模板]三维凸包 给出空间中 \(n\) 个点 \(p_i\),求凸包表面积. 数据范围:\(1\le n\le 2000\). 这篇题解因为是世界上最逊的人写的,所以也会有求凸包体积 ...

  6. 题解-洛谷P4859 已经没有什么好害怕的了

    洛谷P4859 已经没有什么好害怕的了 给定 \(n\) 和 \(k\),\(n\) 个糖果能量 \(a_i\) 和 \(n\) 个药片能量 \(b_i\),每个 \(a_i\) 和 \(b_i\) ...

  7. 题解-洛谷P5217 贫穷

    洛谷P5217 贫穷 给定长度为 \(n\) 的初始文本 \(s\),有 \(m\) 个如下操作: \(\texttt{I x c}\),在第 \(x\) 个字母后面插入一个 \(c\). \(\te ...

  8. 题解 洛谷 P2010 【回文日期】

    By:Soroak 洛谷博客 知识点:模拟+暴力枚举 思路:题目中有提到闰年然后很多人就认为,闰年是需要判断的其实,含有2月29号的回文串,前四位是一个闰年那么我们就可以直接进行暴力枚举 一些小细节: ...

  9. 题解 洛谷P2158 【[SDOI2008]仪仗队】

    本文搬自本人洛谷博客 题目 本文进行了一定的更新 优化了 Markdown 中 Latex 语句的运用,加强了可读性 补充了"我们仍不曾知晓得 消失的 性质5 ",加强了推导的严谨 ...

随机推荐

  1. 表格中的td内的div的文字内容禁止换行一行显示的css

    td { white-space: nowrap } td div { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ...

  2. C++练习 | 二分练习

    Codeforces 371C : Hamburgers #include<bits/stdc++.h> using namespace std; char B='B',S='S',C=' ...

  3. docker故障问题修复

    systemctl start docker启动 systemctl restart docker重启 执行 vi /etc/sysconfig/selinux , 把 selinux 属性值改为di ...

  4. jQuery实现全选、不选和反选功能

    jQuery结合Font Awesome字体图标实现全选.不选和反选功能 Font Awesome字体图标链接地址:http://www.fontawesome.com.cn/faicons/ 效果: ...

  5. 报错: WARN hdfs.DFSClient: Caught exception java.lang.InterruptedException

    WARN hdfs.DFSClient: Caught exception java.lang.InterruptedException 而且日志中没有错误. 官网语句:$ bin/hdfs dfs ...

  6. STM32(5)——通用定时器基本定时器

    1.STM32的Timer简介 STM32中一共有11个定时器,其中2个高级控制定时器,4个普通定时器和2个基本定时器,以及2个看门狗定时器和1个系统嘀嗒定时器. 其中系统嘀嗒定时器是前文中所描述的S ...

  7. homebrew 使用心得

    ''' 安装anaconda 安装命令: brew search anaconda brew cask install anaconda 添加环境变量: vi ~/.bash_profile expo ...

  8. mysql5.6升级为mysql5.7部署jboss/wildfly应用项目

    一.部署mysql5.7二进制版 解压tar -xvf mv mysql-5.7  /usr/local/mysql5.7  或者其他文件夹 cd  /usr/local/mysql.57 usera ...

  9. SSM 框架基于ORACLE集成TKMYBATIS 和GENERATOR自动生成代码(Github源码)

    基于前一个博客搭建的SSM框架 https://www.cnblogs.com/jiangyuqin/p/9870641.html 源码:https://github.com/JHeaven/ssm- ...

  10. Asp.net Core Startup Class中是如何获取配置信息的

    默认的网站构建方式 VS2015新建asp.net core项目,项目建立完成后,有两个文件,Program.cs和Startup.cs. public class Program { public ...