题目大意:给出一个有向图,有N个结点,M条边。
如果从p到q有一条有向边组成的路径,则称结点p可以到达结点q。
输入数据保证初始状态时,每对可达结点之间存在唯一路径。
图中有一个中心点R,它可以到达所有结点。
任务:
(1)求出每个顶点能够到达的顶点数量(包括自身)
(2)最少加多少条边,使得所有顶点之间均可以相互到达,且是唯一路径。
样例
11 12 3
3 2
2 1
2 4
4 5
4 6
6 2
6 7
3 8
8 9
9 10
9 11
10 8
样例输出
1 6 11 6 1 6 1 4 4 4 1
5
1 3
5 4
7 6
11 9
8 3
首先呢,说明一下:当存在两组无交集的边都能使u到达v时,u到v的路径不唯一
这是我目前觉得最精准的解释了。。。
由于初始图路径保证唯一,所以这个图必然只有树边和返祖边。
A任务:
在tarjan的DFS过程中记录一下本节点只通过树边能到的节点数量,再记录一下最多只通过一条返祖边能到达最高的(即DFN最小)节点。再次DFS,找到该节点能到的最高的节点。答案就是该节点能到的最高的节点的能到达的节点的数量(为什么DFN最小就最优?此处留坑)
至于为什么要两次DFS?是因为第一次DFS时,当你更新时,通过多条反向边的必定是还未走过的,所以需要2次DFS。此处继续留坑
B任务:
说白了,就是问你加哪些边可以使原图变成一堆简单环,且原图的每条边只经过一次。
由于加的边数要最小,显然从叶子节点尽可能往上走是最优的。
我们设想从每个出度为0的点开始出发,沿着树边的反向边走,遍历出要加入它的环的边(拓扑的思想),直到马上要走的下一个边已被走过,这时候我们就到了能走的最高点了,然后就在出发点和这个点间连一条边。
但在这之前,由于图上已经存在着一些环,我们要给它们打上已被走过的标记。
还有,走的时候记着要让被走到的点的初度-1,如果初度为0则入队
此处以1为例,我们从1出发,走到它的父亲2,然后给这条边打上标记。再走到3,这时候我们已经到头了,所以1和3连一条边
再以5为例,我们从5出发,走到4,4-6这条边已经在环上了,不能走了所以我们在5和4间连一条边
再以2为例,2-3这条边已经被1走过了,所以不能走,所以2和2之间连一条边,所以不连边
好了,上程序
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<queue>
using namespace std;
const int N=100005,M=1000005;
int head[N],fa[N],cnt;//邻接表用
struct node{
int u,v,next;
}edge[M];
bool vis[N];//在dfs1中表示该点是否被访问,dfs2中表示该点连向父亲的边是否已访问
int dfn[N],low[N];
bool instack[N];
int tot;//dfn时间
int son[N],high[N],highest[N];//A任务的那几个东西
int scccnt,scc[N];//储存强连通分量
int e[N];//出度
int outc,out[N][2];//输出
stack<int> S;
queue<int> Q;
int n,m,r;
void add_edge(int u,int v){
cnt++;
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
void dfs(int u){
low[u]=dfn[u]=++tot;
son[u]=1;
instack[u]=1;
high[u]=u;
S.push(u);
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
dfs(v);
son[u]+=son[v];
fa[v]=u;
e[u]++;
low[u]=min(low[u],low[v]);
if(dfn[high[u]]>dfn[high[v]])
high[u]=high[v];
}
else{
if(dfn[high[u]]>dfn[high[v]])
high[u]=high[v];
}
if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
scccnt++;
int x;
do{
x=S.top();
S.pop();
scc[x]=scccnt;
instack[x]=0;
}while(x!=u);
}
}
void dfs1(int u){
vis[u]=1;
int v;
if(high[u]==u)
highest[u]=u;
else
highest[u]=highest[high[u]];
for(int i=head[u];i;i=edge[i].next){
v=edge[i].v;
if(!vis[v])
dfs1(v);
}
}
void dfs2(){
vis[r]=1;
for(int i=1;i<=n;i++){
if(e[i]==0)
Q.push(i);
if(scc[i]==scc[fa[i]]){
e[fa[i]]--;
if(e[fa[i]]==0)
Q.push(fa[i]);
vis[i]=1;
}
}
while(!Q.empty()){
int x=Q.front();
Q.pop();
int x1=x;
while(!vis[x1]){
vis[x1]=1;
e[x1]--;
x1=fa[x1];
}
if(x==x1)
continue ;
e[x1]--;
if(e[x1]==0)
Q.push(x1);
outc++;
out[outc][0]=x;
out[outc][1]=x1;
}
}
int main()
{
//freopen("network.in","r",stdin);
//freopen("network.out","w",stdout);
scanf("%d%d%d",&n,&m,&r);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
add_edge(a,b);
}
dfs(r);
tot=0;
dfs1(r);
printf("%d",son[highest[1]]);
for(int i=2;i<=n;i++)
printf(" %d",son[highest[i]]);
puts("");
for(int i=1;i<=n;i++)
if(!scc[i])
scc[i]=++scccnt;
memset(vis,0,sizeof vis);
dfs2();
printf("%d\n",outc);
for(int i=1;i<=outc;i++)
printf("%d %d\n",out[i][0],out[i][1]);
}
顺带附一个某大佬的题解
http://blog.csdn.net/qq_34454069/article/details/77926089