【题解】P4899 [IOI2018] werewolf 狼人
そうやってただ日が暮れるまで語り掛ける本当の言葉
题意
给定一个有向图和若干询问,每次询问是否存在一条满足条件的路径:
-
端点分别为 \(u, v\)
-
前面一段不经过 \([1, L]\) 的结点,后面一段不经过 \([R, N]\) 的结点
思路
kruskal 重构树。
首先有条件判定连通性考虑 kruskal 重构树。
这里前面和后面的问题是镜像的,分别维护两棵 kruskal 重构树即可。
kruskal 重构树的性质:从结点 \(u\) 出发,经过边权不超过 \(w\) 的边,可以到达的结点 <-> kruskal 重构树上从 \(u\) 出发,经过边权不超过 \(w\) 的边,可以到达的深度最小祖先的子树。
所以这里考虑将 \(u, v\) 在 kruskal 重构树上跳祖先,然后分别判断两棵重构树内 \(u, v\) 的子树是否有交集。
这里有一个人类智慧的想法:先对第二棵树搜一遍,然后将 \(dfs\) 序附在第一棵树的对应点上,跑一次线段树合并,查询只需在线段树上询问子树对应的区间就行。
时间复杂度懒得算,反正飞快。
代码
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 5;
const int maxm = 4e5 + 5;
const int t_sz = maxm * 40;
const int lg_sz = 17;
struct Edge
{
int u, v, w;
bool operator < (const Edge& rhs) const { return (w < rhs.w); }
} edge[maxm];
int n, m, q;
int cnt, tot, cur;
int lg[maxm], dfn[maxm], out[maxm], rt[maxm], fa[maxm];
int ls[t_sz], rs[t_sz];
inline int read()
{
int res = 0;
char ch = getchar();
while ((ch < '0') || (ch > '9')) ch = getchar();
while ((ch >= '0') && (ch <= '9'))
{
res = res * 10 + ch - '0';
ch = getchar();
}
return res;
}
void update(int &k, int l, int r, int p)
{
k = ++cnt;
if (l == r) return;
int mid = (l + r) >> 1;
if (p <= mid) update(ls[k], l, mid, p);
else update(rs[k], mid + 1, r, p);
}
bool query(int k, int l, int r, int ql, int qr)
{
if (!k) return false;
if ((l >= ql) && (r <= qr)) return true;
int mid = (l + r) >> 1;
bool res = false;
if (ql <= mid) res |= query(ls[k], l, mid, ql, qr);
if (qr > mid) res |= query(rs[k], mid + 1, r, ql, qr);
return res;
}
int merge(int u, int v)
{
if ((!u) || (!v)) return u | v;
int p = ++cnt;
ls[p] = merge(ls[u], ls[v]);
rs[p] = merge(rs[u], rs[v]);
return p;
}
struct re_tree
{
int c[maxm], ls[maxm], rs[maxm], dep[maxm], f[maxm][lg_sz];
void get_dep(int u)
{
if (ls[u])
{
f[ls[u]][0] = f[rs[u]][0] = u;
dep[ls[u]] = dep[rs[u]] = dep[u] + 1;
get_dep(ls[u]), get_dep(rs[u]);
}
}
void dfs1(int u)
{
dfn[u] = ++cur;
if (ls[u]) dfs1(ls[u]), dfs1(rs[u]);
out[u] = cur;
}
void dfs2(int u)
{
if (ls[u])
{
dfs2(ls[u]), dfs2(rs[u]);
rt[u] = merge(rt[ls[u]], rt[rs[u]]);
}
else update(rt[u], 1, tot, dfn[u]);
}
void init()
{
get_dep(tot);
c[0] = tot + 1;
for (int j = 1; j < 16; j++)
for (int i = 1; i <= tot; i++)
f[i][j] = f[f[i][j - 1]][j - 1];
}
int get_anc(int u, int w)
{
int k = lg[dep[u]];
while (k--)
while (c[f[u][k]] <= w)
u = f[u][k];
return u;
}
} t1, t2;
int get(int x) { return (fa[x] == x ? x : fa[x] = get(fa[x])); }
void build(re_tree &t)
{
sort(edge + 1, edge + m + 1);
tot = n;
for (int i = 1; i <= 2 * n; i++) fa[i] = i;
for (int i = 1, fu, fv; i <= m; i++)
{
fu = get(edge[i].u), fv = get(edge[i].v);
if (fu != fv)
{
fa[fu] = fa[fv] = ++tot;
t.c[tot] = edge[i].w;
t.ls[tot] = fu, t.rs[tot] = fv;
}
}
}
int main()
{
n = read(), m = read(), q = read();
for (int i = 1; i <= m; i++) edge[i].u = read() + 1, edge[i].v = read() + 1;
for (int i = 1; i <= m; i++) edge[i].w = max(edge[i].u, edge[i].v);
build(t1);
for (int i = 1; i <= m; i++) edge[i].w = -min(edge[i].u, edge[i].v);
build(t2);
for (int i = 1; i < (1 << 16); i++) lg[i] = lg[i >> 1] + 1;
for (int i = (1 << 16); i <= tot; i++) lg[i] = 16;
t1.init(), t2.init();
t2.dfs1(tot), t1.dfs2(tot);
for (int i = 1, u, v, l, r; i <= q; i++)
{
u = read() + 1, v = read() + 1, l = read() + 1, r = read() + 1;
u = t2.get_anc(u, -l), v = t1.get_anc(v, r);
printf("%d\n", query(rt[v], 1, tot, dfn[u], out[u]));
}
return 0;
}