题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6604
题目大意:在DAG上,定义出度为0的点为终点,每次询问两个点a,b:有多少种方案 炸掉一个点使得a,b 无法到达任意一个终点。
题解:题意可以转化为,从终点出发,到达a,b有多少必经点,也就是支配点。反向建图,由于度为0的点有多个,用一个点作根节点将这些点连起来,然后按拓扑序构建支配树。
每一次询问的答案 就是
d
e
p
(
a
)
+
d
e
p
(
b
)
−
d
e
p
(
l
c
a
(
a
,
b
)
)
dep(a)+ dep(b) - dep(lca(a,b))
dep(a)+dep(b)−dep(lca(a,b))
如果是一般有向图,就要用tarjan
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int mx = 20;
int t,n,m,q;
vector<int> g[maxn],rg[maxn],h[maxn],tmp;
int f[maxn][mx + 1],ru[maxn],dep[maxn];
void tpsort() {
queue<int> q;
for(int i = 1; i <= n; i++)
if(!ru[i]) {
g[0].push_back(i);rg[i].push_back(0);ru[i]++;
}
q.push(0);
while(!q.empty()) {
int top = q.front();
q.pop();
tmp.push_back(top);
for(int i = 0; i < g[top].size(); i++) {
int v = g[top][i];
ru[v]--;
if(!ru[v]) q.push(v);
}
}
}
int lca(int u,int v) {
if(dep[u] < dep[v]) swap(u,v);
for(int i = mx; i >= 0; i--)
if(dep[f[u][i]] >= dep[v]) u = f[u][i];
if(u == v) return u;
for(int i = mx; i >= 0; i--)
if(f[u][i] != f[v][i]) u = f[u][i],v = f[v][i];
return f[u][0];
}
int main() {
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
for(int i = 0; i <= n; i++) {
g[i].clear();rg[i].clear();h[i].clear();
dep[i] = 0;ru[i] = 0;
}
tmp.clear();
for(int i = 1; i <= m; i++) {
int u,v;
scanf("%d%d",&u,&v);
g[v].push_back(u);ru[u]++;
rg[u].push_back(v);
}
tpsort();
for(int i = 1; i < tmp.size(); i++) {
int v = tmp[i];
int y = rg[v][0];
for(int j = 1; j < rg[v].size(); j++)
y = lca(y,rg[v][j]);
h[y].push_back(v);
f[v][0] = y;dep[v] = dep[y] + 1;
for(int i = 1; i <= mx; i++) f[v][i] = f[f[v][i - 1]][i - 1];
}
scanf("%d",&q);
for(int i = 1; i <= q; i++) {
int u,v,l;
scanf("%d%d",&u,&v);
l = lca(u,v);
printf("%d\n",dep[u] + dep[v] - dep[l]);
}
}
return 0;
}