【luogu P4899】werewolf 狼人(最小生成树)(主席树)
werewolf 狼人
题目链接:luogu P4899
题目大意
给你一个无向图,然后每次要从一个地方走到另外一个地方。
然后你在走的过程中要在一个点转换形态,转换之前你只能走大于等于 L 的点,转换之后你只能走小于等于 R 的点。
然后问你对于每次询问,要你回答能不能走。
思路
首先我们考虑把要走的路分成两段。
在转换之前走的,在转化之后走的。
那我们考虑这么搞,求出转换之前能到的,求出转化之后能从这个地方到终点的(由于是双向边,所以也是从终点能到的)。
然后如果这两个点集有交的话,我们就可以到。
那接着看如何搞,首先不难看出两段其实是同样的道理,你反过来一下就好了。
那就只看转换之前能到的。
那看到只能走大于等于的,那我们考虑这么搞。
用最小生成树的方法,从大到小枚举点,再枚举另一边比它大的边,然后建树,而且让小的作为父亲。
然后你就会发现,\(x\) 子树内的点,就是你保证走的点大于等于 \(x\),而且在 \(x\) 出发能到的点。
然后你考虑在这道题里面,那你从 \(x\) 出发,然后要大于等于 \(l\),那你就可以不断的从 \(x\) 往上跳,跳到不能跳,也就是如果跳了跳到的位置就 \(<l\)。然后再从那个位置的子树,就是你可以到的点了。
然后问题就变成两个树的两个子树,两个数的点有对应关系,问你这两个子树里面是否有一对对应的点。
那子树嘛,自然就想到是 dfs 序上的一段区间。
那我们可以搞个二维网格,如果一对是 \((x,y)\),那这个位置就是 \(1\)。
然后假设你两个数的两个 dfs 序上区间分别是 \([x1,y1],[x2,y2]\),那就是求这个矩阵里面有没有 \(1\)。
然后发现每个行或列只会有一个 \(1\),所以我们考虑用主席树来搞。
然后就好啦。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 200001;
const int M = 400001;
const int Q = 200001;
struct node {
int to, nxt;
}e[M << 1];
int n, m, q, x, y, le[N], KK, l, r;
struct ababtree {
node ee[N];
int lee[N], KKK, fa[N][21], fath[N];
int dfn[N], tmp, st[N], ed[N], deg[N];
void clean() {
for (int i = 1; i <= n; i++) fath[i] = i;
}
int find(int now) {
if (fath[now] == now) return now;
return fath[now] = find(fath[now]);
}
void add(int x, int y) {
ee[++KKK] = (node){y, lee[x]}; lee[x] = KKK;
}
void build(bool op) {//搞最小生成树
int kn = 0;
if (!op) {
for (int i = n; i >= 1; i--) {
for (int j = le[i]; j; j = e[j].nxt)
if (e[j].to > i) {
int x = find(i), y = find(e[j].to);
if (x != y) {
fath[y] = x; add(x, y);
kn++; if (kn == n - 1) return ;
}
}
}
}
else {
for (int i = 1; i <= n; i++) {
for (int j = le[i]; j; j = e[j].nxt)
if (e[j].to < i) {
int x = find(i), y = find(e[j].to);
if (x != y) {
fath[y] = x; add(x, y);
kn++; if (kn == n - 1) return ;
}
}
}
}
}
void dfs(int now, int father) {
st[now] = ++tmp; dfn[tmp] = now;
fa[now][0] = father;
deg[now] = deg[father] + 1;
for (int i = lee[now]; i; i = ee[i].nxt)
dfs(ee[i].to, now);
ed[now] = tmp;
}
void bz() {
for (int i = 1; i <= 20; i++)
for (int j = 1; j <= n; j++)
fa[j][i] = fa[fa[j][i - 1]][i - 1];
}
int jmp(int x, int y, bool op) {//跳到临界点
if (!op) {
for (int i = 20; i >= 0; i--)
if (fa[x][i] >= y) x = fa[x][i];
}
else {
for (int i = 20; i >= 0; i--)
if (fa[x][i] && fa[x][i] <= y) x = fa[x][i];
}
return x;
}
}A, B;
struct zxtree {//主席树
int ls[N << 6], rs[N << 6], val[N << 6], tot;
int copy(int x) {
int y = ++tot;
ls[y] = ls[x]; rs[y] = rs[x]; val[y] = val[x];
return y;
}
void up(int now) {
val[now] = val[ls[now]] + val[rs[now]];
}
int insert(int now, int l, int r, int pl, int va) {
now = copy(now);
if (l == r) {
val[now] += va;
return now;
}
int mid = (l + r) >> 1;
if (pl <= mid) ls[now] = insert(ls[now], l, mid, pl, va);
else rs[now] = insert(rs[now], mid + 1, r, pl, va);
up(now);
return now;
}
int query(int now, int l, int r, int L, int R) {
if (!now) return 0;
if (L <= l && r <= R) return val[now];
int mid = (l + r) >> 1, re = 0;
if (L <= mid) re += query(ls[now], l, mid, L, R);
if (mid < R) re += query(rs[now], mid + 1, r, L, R);
return re;
}
}T;
int rt[N];
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
e[++KK] = (node){x, le[y]}; le[y] = KK;
}
int main() {
scanf("%d %d %d", &n, &m, &q);
for (int i = 1; i <= m; i++) {
scanf("%d %d", &x, &y); x++; y++;
add(x, y);
}
A.clean(); B.clean();
A.build(0); B.build(1);
A.dfs(1, 0); B.dfs(n, 0);
A.bz(); B.bz();
for (int i = 1; i <= n; i++) {//对应的位置
rt[i] = T.insert(rt[i - 1], 1, n, B.st[A.dfn[i]], 1);
}
while(q--) {
scanf("%d %d %d %d", &x, &y, &l, &r);
x++; y++; l++; r++;
l = A.jmp(x, l, 0); r = B.jmp(y, r, 1);
if (T.query(rt[A.ed[l]], 1, n, B.st[r], B.ed[r]) - T.query(rt[A.st[l] - 1], 1, n, B.st[r], B.ed[r])) printf("1\n");
else printf("0\n");
}
return 0;
}