[题解] 洛谷 P3603 雪辉
模拟赛中遇到了这个题,当时我这个沙雕因为把一个\(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\)),依次处理每个区间。因为重叠的部分并不会对答案有影响。现在序列的问题已经解决了。
序列处理部分的代码
int b[N]; // 记录区间上每一个位置属于哪个块
int L[N], R[N]; // 每个块的左右断点
bitset<W> cur; // 查询时用到的全局变量
bitset<W> f[110][110]; // 预处理的 f 数组
void preWork() {
// 预处理
for (int i = 1; i <= n; ++i) {
// 先计算单个块的权值情况
b[i] = (i-1) / B + 1;
f[b[i]][b[i]].set(a[i]);
}
// 处理每个块的左右端点
for (int i = 1; i <= b[n]; ++i)
L[i] = R[i-1] + 1, R[i] = i * B;
R[b[n]] = n; // 最后一个块的右端点要特判
for (int i = 1; i < b[n]; ++i)
for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
f[i][j] = f[i][j-1] | f[j][j];
}
void queryOnBlock(int l, int r) {
if (b[l] == b[r]) {
// 特判左右端点在同一个块内的情况
for (int i = l; i <= r; ++i) cur.set(a[i]);
return;
}
cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
}
int mex(bitset<W> &s) {
// 暴力求 mex
for (int i = 0; i < W; ++i)
if (!s.test(i)) return i;
return 1e9;
}
将树上问题转化为序列问题
树链剖分的板子(我这个蒟蒻写挂的部分)。。。
直接贴代码了
int G[N], ed = 1, w[N]; // 树的存储
struct Edge {
int to, nxt;
Edge() { to = nxt = 0; }
Edge(int to, int nxt) : to(to), nxt(nxt) {}
} e[N<<1];
inline void addEdge(int x, int y) {
e[++ed] = Edge(y, G[x]), G[x] = ed;
e[++ed] = Edge(x, G[y]), G[y] = ed;
}
// 树链剖分相关
int dfn[N]; // dfs 序
int fa[N]; // 父结点
int son[N]; // 重儿子
int top[N]; // 重链顶端
int size[N]; // 子数大小
int dep[N]; // 深度
int a[N]; // 转化的序列
void dfs1(int x, int p) {
size[x] = 1, fa[x] = p;
for (int i = G[x]; i != 0; i = e[i].nxt) {
int y = e[i].to;
if (y == p) continue;
dep[y] = dep[x] + 1;
dfs1(y, x);
size[x] += size[y];
if (size[son[x]] < size[y])
son[x] = y;
}
}
void dfs2(int x, int t) {
static int cur = 0;
dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
if (!son[x]) return;
dfs2(son[x], t);
for (int i = G[x]; i != 0; i = e[i].nxt) {
int y = e[i].to;
if (y == son[x] || y == fa[x]) continue;
dfs2(y, y);
}
}
void queryOnTree(int x, int y) {
// 树上查询
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
queryOnBlock(dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
if (dfn[x] > dfn[y]) swap(x, y);
queryOnBlock(dfn[x], dfn[y]);
}
总代码
// 2598ms 57.76MB 无O2
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <bitset>
#define LL long long
using namespace std;
inline int getint() {
int x = 0, f = 1; char c = getchar();
while (!isdigit(c)) { if (c == '-') f = 0; c = getchar(); }
while (isdigit(c)) { x = (x*10) + (c-'0'); c = getchar(); }
return f ? x : -x;
}
const int N = 1e5 + 10;
const int B = 1e3;
const int W = 30001;
int n, m, flag; // flag 判断是否需要异或
int ans1, ans2, lastans; // ans1 为权值种类,ans2 为权值 mex
int G[N], ed = 1, w[N]; // 树的存储
struct Edge {
int to, nxt;
Edge() { to = nxt = 0; }
Edge(int to, int nxt) : to(to), nxt(nxt) {}
} e[N<<1];
inline void addEdge(int x, int y) {
e[++ed] = Edge(y, G[x]), G[x] = ed;
e[++ed] = Edge(x, G[y]), G[y] = ed;
}
// 树链剖分相关
int dfn[N]; // dfs 序
int fa[N]; // 父结点
int son[N]; // 重儿子
int top[N]; // 重链顶端
int size[N]; // 子数大小
int dep[N]; // 深度
int a[N]; // 转化的序列
void dfs1(int x, int p) {
size[x] = 1, fa[x] = p;
for (int i = G[x]; i != 0; i = e[i].nxt) {
int y = e[i].to;
if (y == p) continue;
dep[y] = dep[x] + 1;
dfs1(y, x);
size[x] += size[y];
if (size[son[x]] < size[y])
son[x] = y;
}
}
void dfs2(int x, int t) {
static int cur = 0;
dfn[x] = ++cur, a[cur] = w[x], top[x] = t;
if (!son[x]) return;
dfs2(son[x], t);
for (int i = G[x]; i != 0; i = e[i].nxt) {
int y = e[i].to;
if (y == son[x] || y == fa[x]) continue;
dfs2(y, y);
}
}
int b[N]; // 记录区间上每一个位置属于哪个块
int L[N], R[N]; // 每个块的左右断点
bitset<W> cur; // 查询时用到的全局变量
bitset<W> f[110][110]; // 预处理的 f 数组
void preWork() {
// 预处理
for (int i = 1; i <= n; ++i) {
// 先计算单个块的权值情况
b[i] = (i-1) / B + 1;
f[b[i]][b[i]].set(a[i]);
}
// 处理每个块的左右端点
for (int i = 1; i <= b[n]; ++i)
L[i] = R[i-1] + 1, R[i] = i * B;
R[b[n]] = n; // 最后一个块的右端点要特判
for (int i = 1; i < b[n]; ++i)
for (int j = i+1; j <= b[n]; ++j) // 计算 f 数组
f[i][j] = f[i][j-1] | f[j][j];
}
void queryOnBlock(int l, int r) {
if (b[l] == b[r]) {
// 特判左右端点在同一个块内的情况
for (int i = l; i <= r; ++i) cur.set(a[i]);
return;
}
cur |= f[b[l]+1][b[r]-1]; // 两块之间的部分直接查询
for (int i = l; i <= R[b[l]]; ++i) cur.set(a[i]); // 左边的剩余部分
for (int i = L[b[r]]; i <= r; ++i) cur.set(a[i]); // 右边的剩余部分
}
void queryOnTree(int x, int y) {
// 树上查询
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) swap(x, y);
queryOnBlock(dfn[top[x]], dfn[x]);
x = fa[top[x]];
}
if (dfn[x] > dfn[y]) swap(x, y);
queryOnBlock(dfn[x], dfn[y]);
}
int mex(bitset<W> &s) {
// 暴力求 mex
for (int i = 0; i < W; ++i)
if (!s.test(i)) return i;
return 1e9;
}
int main() {
cin >> n >> m >> flag;
for (int i = 1; i <= n; ++i) w[i] = getint();
for (int i = 1; i < n; ++i) {
int x = getint(), y = getint();
addEdge(x, y);
}
dfs1(1, 0), dfs2(1, 1);
preWork();
for (int i = 1; i <= m; ++i) {
cur.reset();
int num = getint();
for (int j = 1; j <= num; ++j) {
int x = getint() ^ (flag*lastans);
int y = getint() ^ (flag*lastans);
queryOnTree(x, y);
}
ans1 = cur.count(), ans2 = mex(cur);
lastans = ans1 + ans2;
printf("%d %d\n", ans1, ans2);
}
return 0;
}
[题解] 洛谷 P3603 雪辉的更多相关文章
- 题解 洛谷P5018【对称二叉树】(noip2018T4)
\(noip2018\) \(T4\)题解 其实呢,我是觉得这题比\(T3\)水到不知道哪里去了 毕竟我比较菜,不大会\(dp\) 好了开始讲正事 这题其实考察的其实就是选手对D(大)F(法)S(师) ...
- 题解 洛谷 P3396 【哈希冲突】(根号分治)
根号分治 前言 本题是一道讲解根号分治思想的论文题(然鹅我并没有找到论文),正 如论文中所说,根号算法--不仅是分块,根号分治利用的思想和分块像 似却又不同,某一篇洛谷日报中说过,分块算法实质上是一种 ...
- 题解-洛谷P5410 【模板】扩展 KMP(Z 函数)
题面 洛谷P5410 [模板]扩展 KMP(Z 函数) 给定两个字符串 \(a,b\),要求出两个数组:\(b\) 的 \(z\) 函数数组 \(z\).\(b\) 与 \(a\) 的每一个后缀的 L ...
- 题解-洛谷P4229 某位歌姬的故事
题面 洛谷P4229 某位歌姬的故事 \(T\) 组测试数据.有 \(n\) 个音节,每个音节 \(h_i\in[1,A]\),还有 \(m\) 个限制 \((l_i,r_i,g_i)\) 表示 \( ...
- 题解-洛谷P4724 【模板】三维凸包
洛谷P4724 [模板]三维凸包 给出空间中 \(n\) 个点 \(p_i\),求凸包表面积. 数据范围:\(1\le n\le 2000\). 这篇题解因为是世界上最逊的人写的,所以也会有求凸包体积 ...
- 题解-洛谷P4859 已经没有什么好害怕的了
洛谷P4859 已经没有什么好害怕的了 给定 \(n\) 和 \(k\),\(n\) 个糖果能量 \(a_i\) 和 \(n\) 个药片能量 \(b_i\),每个 \(a_i\) 和 \(b_i\) ...
- 题解-洛谷P5217 贫穷
洛谷P5217 贫穷 给定长度为 \(n\) 的初始文本 \(s\),有 \(m\) 个如下操作: \(\texttt{I x c}\),在第 \(x\) 个字母后面插入一个 \(c\). \(\te ...
- 题解 洛谷 P2010 【回文日期】
By:Soroak 洛谷博客 知识点:模拟+暴力枚举 思路:题目中有提到闰年然后很多人就认为,闰年是需要判断的其实,含有2月29号的回文串,前四位是一个闰年那么我们就可以直接进行暴力枚举 一些小细节: ...
- 题解 洛谷P2158 【[SDOI2008]仪仗队】
本文搬自本人洛谷博客 题目 本文进行了一定的更新 优化了 Markdown 中 Latex 语句的运用,加强了可读性 补充了"我们仍不曾知晓得 消失的 性质5 ",加强了推导的严谨 ...
随机推荐
- 用 S5PV210 学习 Linux (二) 刷机(二)
1.在 Ubuntu 下 ,进入 dnw-linux-master\src\driver 文件下,make 截图 如下: 2.紧接着 加载该模块到内核(注意:需要root权限),sudo insmo ...
- HTML5手机端拍照上传
1.accept="image/*" capture="camera" 自动调用手机端拍照功能 accept="image/*" captu ...
- SPOJ 4487. Can you answer these queries VI splay
题目链接:点击打开链接 题意比較明显,不赘述. 删除时能够把i-1转到根,把i+1转到根下 则i点就在 根右子树 的左子树,且仅仅有i这一个 点 #include<stdio.h> #in ...
- 大话Linux内核中锁机制之信号量、读写信号量
大话Linux内核中锁机制之信号量.读写信号量 在上一篇博文中笔者分析了关于内存屏障.读写自旋锁以及顺序锁的相关内容,本篇博文将着重讨论有关信号量.读写信号量的内容. 六.信号量 关于信号量的内容,实 ...
- 如何快速找到指定端口被哪个程序占用并释放该端口(解决bindException)
首先打开打开任务管理器,选择性能模块,下方有打开资源监视器,或者直接搜索资源监视器 在资源监视器中点击侦听端口模块,即可看到正在使用网络端口的应用程序名和pid,如果被占用可以直接使用命令行关闭即可 ...
- AutoMapper 帮助类
AutoMapper帮助类 /// <summary> /// AutoMapper帮助类 /// </summary> public static class AutoMap ...
- 【Dubbo源码阅读系列】之 Dubbo SPI 机制
最近抽空开始了 Dubbo 源码的阅读之旅,希望可以通过写文章的方式记录和分享自己对 Dubbo 的理解.如果在本文出现一些纰漏或者错误之处,也希望大家不吝指出. Dubbo SPI 介绍 Java ...
- 怎么在苹果Mac虚拟机上安装Win7
怎么在苹果Mac虚拟机上安装Win7 使用 Mac 系统的用户,因为一些软件或是应用的原因,可能需要 Windows 系统才能完成.那如果不想在自己的 Mac 电脑上安装双启动系统的话,我们还可以在 ...
- C++11 initializer_list 和 Range-based for loop 学习理解
win10 + vs2017 源码如下: int main() { vector< int > numbers = { 1, 2, 3, 4, 5 }; for (auto num : n ...
- Linux单用户CS模型TCP通讯完全注释手册
Linux单用户CS模型TCP通讯完全注释手册 server 描述 实现一个简单的Linux单用户CS通讯,客户端发送一串字符串,服务器将其转换为大写后返回. server 代码 ``` #inclu ...