【IOI 2018】Werewolf 狼人
虽然作为IOI的Day1T3,但其实不是一道很难的题,或者说这道题其实比较套路吧。
接下来讲解一下这个题的做法:
如果你做过NOI 2018的Day1T1,并且看懂了题面,那你很快就会联想到这道题,因为两者都是问某一个点能到达的点集,只不过限制在点上还是边上的问题。
$Kruskal$重构树可以把在边上的限制转化成点上的,于是能解决NOI 2018的Day1T1;那么这道题就可以直接做,因为限制已经在点上了。
具体来讲,从$s$点只能走编号$>=l$的点,那么我们就构建一棵树,使得任意一个非根节点的标号都比它父亲都大。要做到这一点,只要从大到小枚举点$x$,尝试把它加入树中,枚举所有与他相邻且编号比它大的节点$y$,如果$x$和$y$不在一个连通块里,就让$y$的并查集的根成为$x$的儿子,并在并查集里也连起来。
我们要对$s$和$t$都做一遍,也就是从大到小建一棵树$Tu$,从小到大建一棵树$Tv$。那么从$s$出发只走$>=l$的点能走到的点在$Tu$上就是一个子树;对$t$同理。
我们能用树上倍增找到$s$能走到的$Tu$上的那个子树,和$t$能走到的$Tv$的那个子树,这两者在两棵树上分别都对应$Dfs$序上的一段区间。
我们需要找到一个能变身的地点,它要满足能从$s$和$t$都能走到它,也就是在这两段$Dfs$上的区间中存在相同的元素。如何判断两个序列上的两个区间是否同时拥有某个元素,一般情况下我不会做,但是由于每个元素都恰好在一个序列中出现了一次,我们可以利用这个特殊的性质来解决。一个询问的答案是$1$当且仅当存在一个点,其中它在第一个序列中的位置在所求区间内,第二个序列也是。我们发现这就是一个简单的二维数点,我们把所有点在第一个和第二个$Dfs$序中中出现的位置分别当成$(x,y)$,一个询问就是判定一个二维平面上的一个矩形内是否有点。用离线树状数组实现比较方便。
$\bigodot$技巧&套路:
- 点上限制和$Kruskal$重构树在边上的限制的联系
- 两个$Dfs$序区间是否有相同元素向数点问题的转化
由于这道题本质是一道传统题,所以我用了传统的实现方法:
#include <cstdio>
#include <vector>
#include <algorithm> using namespace std; const int LOG = ;
const int N = ; int n, m, nq, qi;
int ans[N];
vector<int> g[N]; struct Que {
int x, y, id, ty, f;
friend bool operator < (const Que &a, const Que &b) {
return (a.x != b.x)? (a.x < b.x) : (a.ty < b.ty);
}
} q[N * ]; struct Dsu {
int fa[N];
void Init(int n) {
for (int i = ; i <= n; ++i) fa[i] = i;
}
int Sk(int x) {
return fa[x] == x? x : fa[x] = Sk(fa[x]);
}
} U, V; struct Tree {
vector<int> g[N];
int tot, din[N], dfn[N], row[N], ed[N], dep[N], gr[LOG][N];
void Add(int x, int y) {
g[x].push_back(y), ++din[y];
}
void Dfs(int x) {
dfn[x] = ++tot, row[tot] = x;
for (int i = ; i < LOG; ++i)
if (gr[i - ][x]) gr[i][x] = gr[i - ][gr[i - ][x]];
for (int v : g[x]) {
gr[][v] = x, dep[v] = dep[x] + ;
Dfs(v);
}
ed[x] = tot;
}
void Build() {
int rt = -;
for (int i = ; i <= n; ++i) if (din[i] == ) rt = i;
if (rt == -) throw;
Dfs(rt);
}
} Tu, Tv; int Fly_u(int x, int lim) {
for (int i = LOG - ; ~i; --i)
if (Tu.gr[i][x] && Tu.gr[i][x] >= lim) x = Tu.gr[i][x];
return x;
}
int Fly_v(int x, int lim) {
for (int i = LOG - ; ~i; --i)
if (Tv.gr[i][x] && Tv.gr[i][x] <= lim) x = Tv.gr[i][x];
return x;
} namespace BIT {
int t[N];
void Add(int x) {
for (; x <= n; x += x & -x) ++t[x];
}
int Qr(int x, int r = ) {
for (; x; x -= x & -x) r += t[x];
return r;
}
} int main() {
scanf("%d%d%d", &n, &m, &nq);
for (int i = , x, y; i <= m; ++i) {
scanf("%d%d", &x, &y);
g[++x].push_back(++y), g[y].push_back(x);
}
U.Init(n), V.Init(n);
for (int i = n; i >= ; --i) {
for (int j : g[i]) {
if (j < i) continue;
int y = U.Sk(j);
if (i != y) U.fa[y] = i, Tu.Add(i, y);
}
}
for (int i = ; i <= n; ++i) {
for (int j : g[i]) {
if (j > i) continue;
int y = V.Sk(j);
if (i != y) V.fa[y] = i, Tv.Add(i, y);
}
}
Tu.Build(), Tv.Build();
for (int i = ; i <= n; ++i) {
q[i].x = Tu.dfn[i], q[i].y = Tv.dfn[i];
q[i].ty = ;
}
qi = n;
for (int i = , x, y, l, r; i <= nq; ++i) {
scanf("%d%d%d%d", &x, &y, &l, &r);
++x, ++y, ++l, ++r;
int u = Fly_u(x, l), v = Fly_v(y, r);
q[++qi] = (Que){ Tu.ed[u], Tv.ed[v], i, , };
if (Tu.dfn[u] > ) q[++qi] = (Que){ Tu.dfn[u] - , Tv.ed[v], i, , - };
if (Tv.dfn[v] > ) q[++qi] = (Que){ Tu.ed[u], Tv.dfn[v] - , i, , - };
if (Tu.dfn[u] > && Tv.dfn[v] > ) q[++qi] = (Que){ Tu.dfn[u] - , Tv.dfn[v] - , i, , };
}
sort(q + , q + + qi);
for (int i = ; i <= qi; ++i) {
if (q[i].ty == ) {
BIT::Add(q[i].y);
} else {
ans[q[i].id] += q[i].f * BIT::Qr(q[i].y);
}
}
for (int i = ; i <= nq; ++i) {
if (ans[i] < ) throw;
printf("%d\n", (bool)ans[i]);
} return ;
}
【IOI 2018】Werewolf 狼人的更多相关文章
- [IOI 2018] Werewolf
[题目链接] https://www.luogu.org/problemnew/show/P4899 [算法] 建出原图的最小/最大生成树的kruskal重构树然后二维数点 时间复杂度 ...
- [IOI2018] werewolf 狼人
[IOI2018] werewolf 狼人 IOI2018题解 (其实原题强制在线,要用主席树) 代码: 注意: 1.下标从0~n-1 2.kruskal重构树开始有n个节点,tot从n开始,++to ...
- [IOI2018] werewolf 狼人 kruskal重构树,主席树
[IOI2018] werewolf 狼人 LG传送门 kruskal重构树好题. 日常安利博客文章 这题需要搞两棵重构树出来,这两棵重构树和我们平时见过的重构树有点不同(据说叫做点权重构树?),根据 ...
- [LOJ2865] P4899 [IOI2018] werewolf 狼人
P4899 [IOI2018] werewolf 狼人 LOJ#2865.「IOI2018」狼人,第一次AC交互题 kruskal 重构树+主席树 其实知道重构树的算法的话,难度就主要在主席树上 习惯 ...
- 【IOI 2018】Combo 组合动作(模拟,小技巧)
题目链接 IOI的签到题感觉比NOI的签到题要简单啊,至少NOI同步赛我没有签到成功…… 其实这个题还是挺妙妙的,如果能够从题目出发,利用好限制,应该是可以想到的做法的. 接下来开始讲解具体的做法: ...
- 【IOI 2018】Highway 高速公路收费
这是一道极好的图论题,虽然我一开始只会做$18$分,后来会做$51$分,看着题解想了好久才会做(吐槽官方题解:永远只有一句话),但这的确是一道好题,值得思考,也能启发思维. 如果要讲这道题,还是要从部 ...
- 【IOI 2018】Doll 机械娃娃
我感觉这个题作为Day2T1,有一定的挑战性.为$Rxd$没有完成这道题可惜. 我觉得这道题,如果按照前几个部分分的思路来想,就有可能绕进错误的思路中.因为比如说每个传感器最多只在序列中出现$2$次, ...
- [Luogu4899][IOI2018] werewolf 狼人
luogu sol \(\mbox{IOI2018}\)的出题人有没有看过\(\mbox{NOI2018}\)的题目呀... \(\mbox{Kruskal}\)重构树+二维数点. 题目相当于是问你从 ...
- P4899 【[IOI2018] werewolf 狼人】
感觉已经几次碰到这种类型的题目了,写篇\(Blog\)总结一下 题意: 是否存在一条\((s_i, t_i)\)的路径,满足先只走编号不超过\(L_i\)的点,再走编号不超过\(R_i\)的点 \(S ...
随机推荐
- 【坚持】Selenium+Python学习之从读懂代码开始 DAY6
2018/05/23 Python内置的@property装饰器 [@property](https://www.programiz.com/python-programming/property) ...
- etcd集群证书安装过程一
为确保安全,kubernetes 系统各组件需要使用 x509 证书对通信进行加密和认证. CA (Certificate Authority) 是自签名的根证书,用来签名后续创建的其它证书. 本文档 ...
- 比较语义分割的几种结构:FCN,UNET,SegNet,PSPNet和Deeplab
简介 语义分割:给图像的每个像素点标注类别.通常认为这个类别与邻近像素类别有关,同时也和这个像素点归属的整体类别有关.利用图像分类的网络结构,可以利用不同层次的特征向量来满足判定需求.现有算法的主要区 ...
- vue+webpack前端开发项目的安装方法
安装前,需要进行node.npm检测,查看是否已有安装node.npm环境: 操作方法:Windows+R 调出运行框,输入cmd 调出命令框:分别输入node -v 回车(查看node版本) npm ...
- jumpserver安装与部署
1.简介 Jumpserver 是一款由Python编写开源的跳板机(堡垒机)系统,实现了跳板机应有的功能.基于ssh协议来管理,客户端无需安装agent.特点: 完全开源,GPL授权 Pyth ...
- 解读Python编程中的命名空间与作用域
变量是拥有匹配对象的名字(标识符).命名空间是一个包含了变量名称们(键)和它们各自相应的对象们(值)的字典.一个Python表达式可以访问局部命名空间和全局命名空间里的变量.如果一个局部变量和一个全局 ...
- [Notice]博客地址转移 vitostack.com
个人博客地址转移至vitostack.com 这里可能不会经常更新. 欢迎访问新地址.
- 兼容所有浏览器的旋转效果-IE滤镜Matrix和CSS3transform
在现代浏览器中使用CSS3的transform样式即可轻松搞定,但是对于国内IE浏览器(特别是7,8)还占有较大份额的情况下,兼容性还是必须要考虑的,所以也特意记录下IE旋转滤镜的使用. 在IE下的旋 ...
- idea最常使用的快捷键
撤销 反撤销 : Ctrl+Z / Ctrl+Shift+Z 删除一行 : Ctrl+Y 跳到实现类 : Ctrl+Alt+B 重命名文件: shift+F6 控制台放大缩小: ctrl+shif ...
- php 数组去重
php 数组去重 数组中重复项的去除 2010-07-28 15:29 一维数组的重复项: 使用array_unique函数即可,使用实例如下: <?php ...