题意:
给定一个有 n 个点,m 条边的 DAG,再有 q 次询问,每次询问给定两点 a 和 b,问有多少种情况使得破坏一个点后,a 或 b 不能到达出度为 0 的点。(n, q <= 1e5, m <= 2e5)
链接:
https://vjudge.net/problem/HDU-6604
解题思路:
将边反向,则为求入度为 0 的点不能到达 a 或 b 的情况。引入一个 0 号结点连接入度为 0 的点,则问题等价于破坏一个点后 0 号点不能到达 a 或 b,答案则为 0 号点到 a 的必经点点集并上到 b 的必经点点集的大小。构建支配树后, 。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define pb push_back
#define sz(a) ((int)a.size())
#define mem(a, b) memset(a, b, sizeof a)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e5 + 5;
const int maxm = 1e6 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn], P[maxn], V[maxn];
int in[maxn], dep[maxn], fa[maxn][21], tp[maxn];
int n, m, tot;
void bfs() {
queue<int> q;
for (int i = 1; i <= n; ++i) {
if (in[i]) continue;
q.push(i), P[i].pb(0);
}
while (!q.empty()) {
int u = q.front(); q.pop(); tp[++tot] = u;
for (int i = 0; i < sz(G[u]); ++i) {
int v = G[u][i];
if (--in[v] == 0) q.push(v);
P[v].pb(u);
}
}
}
int lca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
for(int i = 20; i >= 0; --i){
if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
}
if(u == v) return u;
for(int i = 20; i >= 0; --i){
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
}
return fa[u][0];
}
void build() {
bfs();
for (int i = 1; i <= n; ++i) {
int u = tp[i], lc = P[u][0];
for (int j = 1; j < sz(P[u]); ++j) {
int v = P[u][j];
lc = lca(lc, v);
}
V[lc].pb(u), dep[u] = dep[lc] + 1, fa[u][0] = lc;
for (int j = 1; j <= 20; ++j) fa[u][j] = fa[fa[u][j - 1]][j - 1];
}
}
void init() {
for (int i = 0; i <= n; ++i) in[i] = 0, G[i].clear(), P[i].clear(), V[i].clear();
tot = 0;
}
int main(){
int t; scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
init();
while(m--){
int u, v; scanf("%d%d", &v, &u);
G[u].pb(v), P[v].pb(u), ++in[v];
}
build();
int q; scanf("%d", &q);
while(q--){
int u, v; scanf("%d%d", &u, &v);
int lc = lca(u, v);
int ret = dep[u] + dep[v] - dep[lc];
printf("%d\n", ret);
}
}
return 0;
}