@description@

在日本的茨城县内共有 N 个城市和 M 条道路。这些城市是根据人口数量的升序排列的,依次编号为 0 到 N-1。每条道路连接两个不同的城市,并且可以双向通行。由这些道路,你能从任意一个城市到另外任意一个城市。

你计划了 Q 个行程,这些行程分别编号为 0 至 Q-1。第 i(0 <= i <= Q - 1)个行程是从城市 Si 到城市 Ei。

你是一个狼人。你有两种形态:人形和狼形。在每个行程开始的时候,你是人形。在每个行程结束的时候,你必须是狼形。在行程中,你必须要变身(从人形变成狼形)恰好一次,而且只能在某个城市内(包括可能是在 Si 或 Ei 内)变身。

狼人的生活并不容易。当你是人形时,你必须避开人少的城市,而当你是狼形时,你必须避开人多的城市。对于每一次行程 i(0 <= i <= Q - 1),都有两个阈值 Li 和 Ri(0 <= Li <= Ri <= N - 1),用以表示哪些城市必须要避开。

准确地说,当你是人形时,你必须避开城市 0, 1, ..., Li - 1;而当你是狼形时,则必须避开城市 Ri + 1, Ri + 2, ..., N - 1。

这就是说,在行程 i 中,你必须在城市 Li, Li+1, ..., Ri 中的其中一个城市内变身。

你的任务是,对每一次行程,判定是否有可能在满足上述限制的前提下,由城市 Si 走到城市 Ei。你的路线可以有任意长度。

输入格式

你需要实现下面的函数:

int[] check_validity(int N, int[] X, int[] Y, int[] S, int[] E, int[] L, int[] R)

N:城市的数量

X 和 Y:两个长度为 M 的数组。对于每个 j(0 <= j <= M - 1),城市 X[j] 都有道路直接连到城市 Y[j]。

S, E, L, 及 R:均为长度为 Q 的数组,以表示行程。

数据范围与提示

2 <= N <= 200000, N - 1 <= M <= 400000, 1 <= Q <= 200000。

0 <= Li <= Si <= N - 1, 0 <= Ei <= Ri <= N - 1, Si ≠ Ei, Li <= Ri。

保证没有重边自环,保证图连通。

@solution@

等价的题意:

从 S 出发只经过 L ~ N-1 的点能够到达的点集,与从 E 出发只经过 0 ~ R 的点能够到达的点集,两个集合是否有交集。

现先考虑从 E 出发只经过 0 ~ R 的点能够到达的点。

如果从点 0 到点 N - 1 依次加入它们向前连的边,并用类似于可持久化并查集的东西维护,只要调用第 R 个版本的并查集就可以得到 E 能到达的点。

可持久化并查集?有没有想起 NOI2018 的 D1T1?是否对于这道题我们也可以转成 kruskal 重构树来做?

考虑对于所有边,记边权为 min{它连接的两个端点的编号},则只经过 0 ~ R 的点等价于只经过边权 <= R 的边。

于是一个点能够到达的点集,通过倍增可以找到 kruskal 重构树中对应的子树,子树内的所有点都是可到达的。

从 S 出发只经过 L ~ N-1 的点能够到达的点集可以类比着建另一棵树。

通过 dfs 序,我们可以把问题转化为:

是否存在一个 (dfn1[x], dfn2[x]),使得 a <= dfn1[x] <= b, c <= dfn2[x] <= d。

经典的二维偏序问题,相当于统计这个二维区域的点数。

我懒得动脑子为了强化代码能力打了个可持久化线段树。

时间复杂度 O(nlogn)。

@accepted code@

#include "werewolf.h"
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define vi vector<int>
#define pb push_back
const int MAXN = 200000;
const int MAXQ = 200000;
const int MAXM = 400000;
int N, M, Q;
struct Graph{
bool type;
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[2*MAXN + 5], *ecnt;
int v[2*MAXN + 5], fa[20][2*MAXN + 5];
Graph(int p) {ecnt = edges, type = p;}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
// printf("! %d %d\n", u, v);
}
void dfs(int x, int f) {
fa[0][x] = f;
for(int i=1;i<20;i++)
fa[i][x] = fa[i-1][fa[i-1][x]];
for(edge *p=adj[x];p;p=p->nxt)
dfs(p->to, x);
}
void build(int x) {v[0] = type ? (MAXN + 5) : 0; dfs(x, 0);}
int query(int x, int k) {
for(int i=19;i>=0;i--)
if( type ) {
if( v[fa[i][x]] <= k )
x = fa[i][x];
}
else {
if( v[fa[i][x]] >= k )
x = fa[i][x];
}
return x;
}
}G1(0), G2(1);
int fa[2*MAXN + 5];
int find(int x) {
return fa[x] = (fa[x] == x ? x : find(fa[x]));
}
vi e[MAXN + 5];
void build1() {
int cnt = N;
for(int i=1;i<(N<<1);i++) fa[i] = i;
for(int i=N;i>=1;i--) {
G1.v[i] = i;
for(int j=0;j<(int)e[i].size();j++)
if( e[i][j] > i ) {
int p = e[i][j], f = find(i), g = find(p);
if( f != g ) {
int x = (++cnt);
G1.addedge(x, f), G1.addedge(x, g);
fa[f] = fa[g] = x;
G1.v[x] = i;
}
}
}
G1.build((N<<1)-1);
}
void build2() {
int cnt = N;
for(int i=1;i<(N<<1);i++) fa[i] = i;
for(int i=1;i<=N;i++) {
G2.v[i] = i;
for(int j=0;j<(int)e[i].size();j++)
if( e[i][j] < i ) {
int p = e[i][j], f = find(i), g = find(p);
if( f != g ) {
int x = (++cnt);
G2.addedge(x, f), G2.addedge(x, g);
fa[f] = fa[g] = x;
G2.v[x] = i;
}
}
}
G2.build((N<<1)-1);
}
struct segtree{
struct node{
int cnt; node *ch[2];
}pl[20*MAXN + 5], *NIL, *ncnt;
segtree() {
NIL = ncnt = pl;
NIL->ch[0] = NIL->ch[1] = NIL;
NIL->cnt = 0;
}
node *insert(node *pre, int l, int r, int ps) {
node *p = (++ncnt); (*p) = (*pre); p->cnt++;
if( l == r ) return p;
int mid = (l + r) >> 1;
if( ps <= mid )
p->ch[0] = insert(pre->ch[0], l, mid, ps);
else p->ch[1] = insert(pre->ch[1], mid + 1, r, ps);
return p;
}
int query(node *L, node *R, int l, int r, int ql, int qr) {
if( ql > r || qr < l ) return 0;
if( ql <= l && r <= qr ) return R->cnt - L->cnt;
int mid = (l + r) >> 1;
return query(L->ch[0], R->ch[0], l, mid, ql, qr) + query(L->ch[1], R->ch[1], mid + 1, r, ql, qr);
}
}T;
segtree::node *rt[MAXN + 5];
int tid[2][MAXN + 5], dfn[2][MAXN + 5], dcnt[2];
int le[2][2*MAXN + 5], ri[2][2*MAXN + 5];
void dfs(const Graph &G, int x, int t) {
if( x <= N ) {
dfn[t][le[t][x] = ri[t][x] = tid[t][x] = (++dcnt[t])] = x;
}
else {
le[t][x] = dcnt[t] + 1;
for(Graph::edge *p=G.adj[x];p;p=p->nxt)
dfs(G, p->to, t);
ri[t][x] = dcnt[t];
}
}
void get() {
build1();
build2();
dfs(G1, (N<<1)-1, 0), dfs(G2, (N<<1)-1, 1);
rt[0] = T.NIL;
for(int i=1;i<=N;i++)
rt[i] = T.insert(rt[i-1], 1, N, tid[1][dfn[0][i]]);
}
bool query(int s, int e, int l, int r) {
int x = G1.query(s, l), y = G2.query(e, r);
return T.query(rt[le[0][x]-1], rt[ri[0][x]], 1, N, le[1][y], ri[1][y]);
}
vi check_validity(int _N, vi X, vi Y, vi S, vi E, vi L, vi R) {
N = _N, Q = S.size(), M = X.size();
for(int i=0;i<M;i++)
e[X[i]+1].pb(Y[i]+1), e[Y[i]+1].pb(X[i]+1);
get(); vi A(Q);
for(int i=0;i<Q;i++)
A[i] = query(S[i]+1, E[i]+1, L[i]+1, R[i]+1);
return A;
}
/*
S -> L ~ N (L <= S)
E -> 1 ~ R (E <= R)
*/

@details@

对于 kruskal 重构树,其实我更愿意将其看作 “可持久化并查集”。

因为它的作用就是将某时刻 t 之前的加边操作执行后,图连通的情况。

但是某种意义上来说,kruskal 重构树的思想更类似于点分树之类的东西。即用数据结构再现某一算法的过程。

不管怎么说,会用就行。

另外,比狠人还多一点——是个狼人。

@loj - 2865@ 「IOI2018」狼人的更多相关文章

  1. 「IOI2018」狼人

    快咕一个月了 咕咕咕 咕咕咕咕 LOJ #2865 Luogu P4899(离线) UOJ #407(强制在线) 题意 给定一棵树和若干组询问$(S,E,L,R)$ 表示你初始在$S$,想到达$E$, ...

  2. 【刷题】LOJ 2863 「IOI2018」组合动作

    题目描述 你在玩一个动作游戏.游戏控制器有 \(4\) 个按键,A.B.X 和 Y.在游戏中,你用组合动作来赚金币.你可以依次按这些按键来完成一个组合动作. 这个游戏有一个隐藏的按键序列,可以表示为由 ...

  3. Loj #2192. 「SHOI2014」概率充电器

    Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...

  4. Loj #3096. 「SNOI2019」数论

    Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...

  5. Loj #3093. 「BJOI2019」光线

    Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...

  6. Loj #3089. 「BJOI2019」奥术神杖

    Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...

  7. Loj #2542. 「PKUWC2018」随机游走

    Loj #2542. 「PKUWC2018」随机游走 题目描述 给定一棵 \(n\) 个结点的树,你从点 \(x\) 出发,每次等概率随机选择一条与所在点相邻的边走过去. 有 \(Q\) 次询问,每次 ...

  8. Loj #3059. 「HNOI2019」序列

    Loj #3059. 「HNOI2019」序列 给定一个长度为 \(n\) 的序列 \(A_1, \ldots , A_n\),以及 \(m\) 个操作,每个操作将一个 \(A_i\) 修改为 \(k ...

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

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

随机推荐

  1. myql 配置项

    提高数据插入速度方法 bulk_insert_buffer_size 默认:8M (8*1024*1024) 参考网址:https://stackoverflow.com/questions/2030 ...

  2. JS random函数深入理解(转载)

    转载自:(本文对读者有帮助的话请移步支持原作者) http://www.cnblogs.com/starof/p/4988516.html 一.预备知识 Math.ceil();  //向上取整. M ...

  3. uva 11300 分金币(利用绝对值加和进行求出最小值)

    //qq 767039957 welcome #include<cstdio> #include<algorithm> #include<vector> #incl ...

  4. tomcat9下载与安装

    tomcat9下载与安装 官网下载地址:https://tomcat.apache.org/ 百度云地址:链接:https://pan.baidu.com/s/109PYcSh-eqTctLAXIsb ...

  5. API安全验证之JWT(JSON WEB TOKEN) OLCMS

    假如www.olcms.com/getUserInfo获取用户信息,你怎么知道当前用户是谁?有人说登陆时候我把他UID写入session了,如果是API接口,没有session怎么办,那么就需要把UI ...

  6. 一款你不容错过的Laravel后台管理扩展包 —— Voyager – Laravel学院

    1.简介 Voyager是一个你不容错过的Laravel后台管理扩展包,提供了CRUD操作.媒体管理.菜单构建.数据管理等操作. 官网:https://the-control-group.github ...

  7. HR招聘_(七)_招聘方法论(面试环节·动机判断)

    候选人选择一般会看硬性技能,软性技能,动机意愿三个方面的匹配程度,硬性技能主要指纵向的业务能力,部门面试官也会着重看这方面,软性技能包括沟通,情商,气质等.动机意愿非常重要,再优秀的如果没有意愿,动机 ...

  8. 定位真机运行能用但是打包成apk就不能用的解决方法

    打包apk的SHA1,与key的SHA1(这是多人开发的通病不同电脑共同开发一个app的常见错误之一)不一致.解决方法: 今天虽然离职了,但是今天遇到的是,当我在用高德地图开发的时候,在Android ...

  9. placeholder改变输入框字体颜色

    ::-webkit-input-placeholder {  color: #888;}:-moz-placeholder {  color: #888;}::-moz-placeholder{col ...

  10. 【linux配置】虚拟机配置静态IP地址

    使用VMware配置虚拟机静态IP地址 一.安装好虚拟后在菜单栏选择编辑→ 虚拟网络编辑器,打开虚拟网络编辑器对话框,选择Vmnet8 Net网络连接方式,随意设置子网IP,点击NAT设置页面,查看子 ...