一、有向无环图:
例①:P2597 [ZJOI2012]灾难:
题目描述
阿米巴是小强的好朋友。
阿米巴和小强在草原上捉蚂蚱。小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难。
学过生物的阿米巴告诉小强,草原是一个极其稳定的生态系统。如果蚂蚱灭绝了,小鸟照样可以吃别的虫子,所以一个物种的灭绝并不一定会引发重大的灾难。
我们现在从专业一点的角度来看这个问题。我们用一种叫做食物网的有向图来描述生物之间的关系:
一个食物网有N个点,代表N种生物,如果生物x可以吃生物y,那么从y向x连一个有向边。
这个图没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
举个例子:在一个草场上,生物之间的关系是:
如
如果小强和阿米巴把草原上所有的羊都给吓死了,那么狼会因为没有食物而灭绝,而小强和阿米巴可以通过吃牛、牛可以通过吃草来生存下去。所以,羊的灾难值是1。但是,如果草突然灭绝,那么整个草原上的5种生物都无法幸免,所以,草的灾难值是4。
给定一个食物网,你要求出每个生物的灾难值。
输入格式
输入文件 catas.in 的第一行是一个正整数 N,表示生物的种数。生物从 1 标
号到 N。
接下来 N 行,每行描述了一个生物可以吃的其他生物的列表,格式为用空
格隔开的若干个数字,每个数字表示一种生物的标号,最后一个数字是 0 表示列
表的结束。
输出格式
输出文件catas.out包含N行,每行一个整数,表示每个生物的灾难值。
输入输出样例
输入 #1 复制
5
0
1 0
1 0
2 3 0
2 0
输出 #1 复制
4
1
0
0
0
说明/提示
【样例说明】
样例输入描述了题目描述中举的例子。
【数据规模】
对50%的数据,N ≤ 10000。
对100%的数据,1 ≤ N ≤ 65534。
输入文件的大小不超过1M。保证输入的食物网没有环。
思路:求有向无环图每个点可以支配的的点数,不包括其自身。就是求支配树上每个点为根的子树的大小。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=2000100;
const int mod=1e9+7;
int head[maxn],ver[maxn],nt[maxn];
int hh[maxn],vv[maxn],nn[maxn];
int d[maxn],f[maxn][22],si[maxn],t,n;
int tot;
int du[maxn],top[maxn];
bool ha[maxn];
void add(int x,int y)
{
ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
vv[tot]=x,nn[tot]=hh[y],hh[y]=tot;
du[y]++;
}
void topo(void)
{
queue<int>q;
for(int i=1;i<=n;i++)
if(du[i]==0) q.push(i);
int cnt=0;
while(q.size())
{
int x=q.front();
q.pop();
top[++cnt]=x;
for(int i=head[x];i;i=nt[i])
{
int y=ver[i];
du[y]--;
if(du[y]==0) q.push(y);
}
}
}
int LCA(int x,int y)
{
if(d[x]>d[y]) swap(x,y);
for(int i=t;i>=0;i--)
if(d[f[y][i]]>=d[x]) y=f[y][i];
if(y==x) return x;
for(int i=t;i>=0;i--)
if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(void)
{
int x,root;
scanf("%d",&n);
root=n+1;
t=log(n)/log(2)+1;
for(int i=1;i<=n;i++)
{
si[i]=1;
while(scanf("%d",&x),x)
{
ha[i]=true;
add(x,i);
}
}
topo();
for(int i=1;i<=n;i++)
{
int x=top[i];
if(!ha[x])
{
d[x]=1;
f[x][0]=root;
continue;
}
int lca=vv[hh[x]];
for(int j=hh[x];j;j=nn[j])
lca=LCA(lca,vv[j]);
d[x]=d[lca]+1;
f[x][0]=lca;
for(int j=1;j<=t;j++)
f[x][j]=f[f[x][j-1]][j-1];
}
for(int i=n;i>=1;i--)
si[f[top[i]][0]]+=si[top[i]];
for(int i=1;i<=n;i++)
printf("%d\n",si[i]-1);
return 0;
}
例②:Blow up the city HDU - 6604:
Country A and B are at war. Country A needs to organize transport teams to deliver supplies toward some command center cities.
In order to ensure the delivery works efficiently, all the roads in country A work only one direction. Therefore, map of country A can be regarded as DAG( Directed Acyclic Graph ). Command center cities only received supplies and not send out supplies.
Intelligence agency of country B is credibly informed that there will be two cities carrying out a critical transporting task in country A.
As long as any one of the two cities can not reach a command center city, the mission fails and country B will hold an enormous advantage. Therefore, country B plans to destroy one of the n cities in country A and all the roads directly connected. (If a city carrying out the task is also a command center city, it is possible to destroy the city to make the mission fail)
Now country B has made q hypotheses about the two cities carrying out the critical task.
Calculate the number of plan that makes the mission of country A fail.
Input
The first line contains a integer T (1≤T≤10), denoting the number of test cases.
In each test case, the first line are two integers n,m, denoting the number of cities and roads(1≤n≤100,000,1≤m≤200,000).
Then m lines follow, each with two integers u and v, which means there is a directed road from city u to v (1≤u,v≤n,u≠v).
The next line is a integer q, denoting the number of queries (1≤q≤100,000)
And then q lines follow, each with two integers a and b, which means the two cities carrying out the critical task are a and b (1≤a,b≤n,a≠b).
A city is a command center if and only if there is no road from it (its out degree is zero).
Output
For each query output a line with one integer, means the number of plan that makes the mission of country A fail.
Sample Input
2
8 8
1 2
3 4
3 5
4 6
4 7
5 7
6 8
7 8
2
1 3
6 7
3 2
3 1
3 2
2
1 2
3 1
Sample Output
4
3
2
2
题解:当时做的时候就知道是支配树,奈何那时不会写。
就是求某两个点到根节点的必经点有多少个,求出每个点到根节点的必经点,再减去重复的就好了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=200100;
const int mod=1e9+7;
int head[maxn],ver[maxn],nt[maxn];
int hh[maxn],vv[maxn],nn[maxn];
int d[maxn],f[maxn][22],t,n,m,q;
int tot;
int du[maxn],top[maxn];
void init(void)
{
memset(head,0,sizeof(head));
memset(hh,0,sizeof(hh));
memset(du,0,sizeof(du));
memset(f,0,sizeof(f));
tot=0;
}
void add(int x,int y)
{
ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
vv[tot]=x,nn[tot]=hh[y],hh[y]=tot;
du[y]++;
}
void topo(void)
{
queue<int>q;
for(int i=1;i<=n;i++)
if(du[i]==0) q.push(i);
int cnt=0;
while(q.size())
{
int x=q.front();
q.pop();
top[++cnt]=x;
for(int i=head[x];i;i=nt[i])
{
int y=ver[i];
du[y]--;
if(du[y]==0) q.push(y);
}
}
}
int LCA(int x,int y)
{
if(d[x]>d[y]) swap(x,y);
for(int i=t;i>=0;i--)
if(d[f[y][i]]>=d[x]) y=f[y][i];
if(y==x) return x;
for(int i=t;i>=0;i--)
if(f[y][i]!=f[x][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int main(void)
{
int tt;
scanf("%d",&tt);
while(tt--)
{
init();
int x,y,root;
scanf("%d%d",&n,&m);
root=n+1;
t=log(n+1)/log(2)+1;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(y,x);
}
topo();
d[root]=0;
for(int i=1;i<=n;i++)
{
int x=top[i];
if(!hh[x])
{
d[x]=d[root]+1;
f[x][0]=root;
continue;
}
int lca=vv[hh[x]];
for(int j=hh[x];j;j=nn[j])
lca=LCA(lca,vv[j]);
d[x]=d[lca]+1;
f[x][0]=lca;
for(int j=1;j<=t;j++)
f[x][j]=f[f[x][j-1]][j-1];
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",d[x]+d[y]-d[LCA(x,y)]);
}
}
return 0;
}
二、有向有环图的必经点:
例①:P5180 【模板】支配树:
题目背景
模板题,无背景
题目描述
给定一张有向图,求从1号点出发,每个点能支配的点的个数(包括自己)
输入格式
第一行两个正整数n,m,表示点数和边数 接下来m行,每行输入两个整数u,v表示有一条u到v的有向边
输出格式
一行输出n个整数,表示每个点能支配的点的个数
输入输出样例
输入 #1 复制
10 15
1 2
2 3
3 4
3 5
3 6
4 7
7 8
7 9
7 10
5 6
6 8
7 8
4 1
3 6
5 3
输出 #1 复制
10 9 8 4 1 1 3 1 1 1
说明/提示
n<2 * 105,m<3 * 10^5
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=300100;
const int mod=1e9+7;
struct node
{
int head[maxn],ver[maxn],nt[maxn];
int tot;
node(){tot=0;}
void add(int x,int y)
{
ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}
}g,rg,semig,dom;
int dfn[maxn],id[maxn],f[maxn],val[maxn],cnt,n,m;
int semi[maxn],idom[maxn],fa[maxn],ans[maxn];
void init(int n)
{
cnt=0;
for(int i=1;i<=n;i++)
f[i]=semi[i]=val[i]=i;
}
void dfs(int x)
{
dfn[x]=++cnt;
id[cnt]=x;
for(int i=g.head[x];i;i=g.nt[i])
{
int y=g.ver[i];
if(dfn[y]) continue;
fa[y]=x;
dfs(y);
}
}
int fi(int x)
{
if(f[x]!=x)
{
int t=f[x];
f[x]=fi(f[x]);
if(dfn[semi[val[t]]]<dfn[semi[val[x]]]) val[x]=val[t];
}
return f[x];
}
void tarjan(void)
{
for(int j=cnt;j>=2;j--)
{
int x=id[j];
for(int i=rg.head[x];i;i=rg.nt[i])
{
int y=rg.ver[i];
if(!dfn[y]) continue;
fi(y);
if(dfn[semi[val[y]]]<dfn[semi[x]])
semi[x]=semi[val[y]];
}
semig.add(semi[x],x);
x=f[x]=fa[x];
for(int i=semig.head[x];i;i=semig.nt[i])
{
int y=semig.ver[i];
fi(y);
if(semi[val[y]]==x) idom[y]=x;
else idom[y]=val[y];
}
}
for(int i=2;i<=cnt;i++)
{
int x=id[i];
if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];
dom.add(idom[x],x);
}
}
void dfs1(int x)
{
ans[x]=1;
for(int i=dom.head[x];i;i=dom.nt[i])
{
int y=dom.ver[i];
dfs1(y);
ans[x]+=ans[y];
}
}
int main(void)
{
scanf("%d%d",&n,&m);
int x,y;
init(n);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
g.add(x,y);
rg.add(y,x);
}
dfs(1);
tarjan();
dfs1(1);
for(int i=1;i<=n;i++)
{
if(i!=1) putchar(' ');
printf("%d",ans[i]);
}
return 0;
}
例②:
Counting on a directed graph CodeChef - GRAPHCNT
题意:
给出一个有 n 个节点,m 条边的有向图,询问有多少对点对 (x,y),满足存在两条路径(一条从 1 到 x,一条从 1 到 y),使得这两条路径的交点只有 1。
数据范围:1≤n≤1e5,m≤5×1e5。
思路:
我们先以 1为根建一颗支配树,可以发现,当 lca(x,y)=1时,(x,y)就是满足条件的点对。
因为如果 lca(x,y)不为 1 ,那么这两条路径都必过 lca(x,y)这个点,不满足题目条件。
那么现在的问题就是如何计算 lca(x,y)=1的点对数量了。
不难看出,只有当 x和 y 位于1 的不同子树时,lca(x,y) 才是 1。因此我们统计出 1 的所有子树的 size就可以了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<deque>
#include<map>
#include<vector>
#include<cmath>
#define ll long long
#define llu unsigned ll
#define pr pair<int,int>
using namespace std;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const int maxn=500100;
const int mod=1e9+7;
struct node
{
int head[maxn],ver[maxn],nt[maxn];
int tot;
node(){tot=0;}
void add(int x,int y)
{
ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}
}g,rg,semig,dom;
int dfn[maxn],id[maxn],f[maxn],val[maxn],cnt,n,m;
int semi[maxn],idom[maxn],fa[maxn],ans[maxn];
ll res=0;
void init(int n)
{
cnt=0;
for(int i=1;i<=n;i++)
f[i]=semi[i]=val[i]=i;
}
void dfs(int x)
{
dfn[x]=++cnt;
id[cnt]=x;
for(int i=g.head[x];i;i=g.nt[i])
{
int y=g.ver[i];
if(dfn[y]) continue;
fa[y]=x;
dfs(y);
}
}
int fi(int x)
{
if(f[x]!=x)
{
int t=f[x];
f[x]=fi(f[x]);
if(dfn[semi[val[t]]]<dfn[semi[val[x]]]) val[x]=val[t];
}
return f[x];
}
void tarjan(void)
{
for(int j=cnt;j>=2;j--)
{
int x=id[j];
for(int i=rg.head[x];i;i=rg.nt[i])
{
int y=rg.ver[i];
if(!dfn[y]) continue;
fi(y);
if(dfn[semi[val[y]]]<dfn[semi[x]])
semi[x]=semi[val[y]];
}
semig.add(semi[x],x);
x=f[x]=fa[x];
for(int i=semig.head[x];i;i=semig.nt[i])
{
int y=semig.ver[i];
fi(y);
if(semi[val[y]]==x) idom[y]=x;
else idom[y]=val[y];
}
}
for(int i=2;i<=cnt;i++)
{
int x=id[i];
if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];
dom.add(idom[x],x);
}
}
void dfs1(int x)
{
ans[x]=1;
for(int i=dom.head[x];i;i=dom.nt[i])
{
int y=dom.ver[i];
dfs1(y);
if(x==1)
res+=1ll*ans[y]*ans[x];
ans[x]+=ans[y];
}
}
int main(void)
{
scanf("%d%d",&n,&m);
int x,y;
init(n);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
g.add(x,y);
rg.add(y,x);
}
dfs(1);
tarjan();
dfs1(1);
printf("%lld\n",res);
return 0;
}