方法一:Tarjan算法
这道题改了好久好久好久...原理不理解果然什么都做不出来啊55555...
先附上大佬博客Orz:https://blog.csdn.net/sinat_35406909/article/details/81318725
Tarjan算法的for循环中,除了!dfn[v]的情况下要标记正向边,反向找边的时候有两种改法:
else if(inst[v])
{
if(low[u]>dfn[v])
{
k=i;
low[u]=dfn[v];
}
}
}
if(k!=-1)
used[k]=true;
或者
else if(inst[v])
{
if(low[u]>low[v])
k=i; //注意在下一句(可能更改了low[u])的上面!
if(low[u]>dfn[v])
low[u]=dfn[v];
}
}
if(k!=-1)
used[k]=true;
这两种改法都过了,但我不是很懂这两种改发的区别。。因为自己Tarjan的原理都不怎么清楚55555...据某大佬说,一个是时间戳,一个是树根,应该是第二种比较合适,保留一条通往树根的路。如果有大佬能解释的比较清楚详尽一点,还请不吝赐教O(∩_∩)O
附上AC代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
typedef pair<int,int>pp;
#define mkp make_pair
#define pb push_back
const int MAXN=50010;
const int MAXM=100010;
int n,m;
struct Edge
{
int from,to,next;
}edge[MAXM];
int head[MAXN],tot;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
edge[tot].from=u;edge[tot].to=v;
edge[tot].next=head[u];head[u]=tot++;
}
int low[MAXN],dfn[MAXN];
int scc;
//int belong[MAXN];
int sta[MAXN],top;
int index;
bool inst[MAXN];
//int num[MAXN];
bool used[MAXM];
void Tarjan(int u)
{
int v;
low[u]=dfn[u]=++index;
sta[top++]=u;
inst[u]=true;
int k=-1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
v=edge[i].to;
if(!dfn[v])
{
Tarjan(v);
used[i]=true;
if(low[u]>low[v])
low[u]=low[v];
}
else if(inst[v])
{
if(low[u]>dfn[v])
{
k=i;
low[u]=dfn[v];
}
}
}
if(k!=-1)
used[k]=true;
if(low[u]==dfn[u])
{
scc++;
do
{
v=sta[--top];
inst[v]=false;
//Belong[v]=scc;
//num[scc]++;
}
while(v!=u);
}
}
void solve()
{
memset(dfn,0,sizeof(dfn));
memset(inst,false,sizeof(inst));
//memset(num,0,sizeof(num));
index=scc=top=0;
memset(used,false,sizeof(used));
for(int i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
/*cout<<"scc="<<scc<<endl;
for(int i=1;i<=n;i++)
cout<<"i="<<i<<" dfn="<<dfn[i]<<" low="<<low[i]<<endl;*/
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
init();
int u,v;
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
}
//cout<<"tot="<<tot<<endl;
solve();
int numm=0;
for(int i=0;i<tot;i++)
{
if(!used[i])
{
printf("%d %d\n",edge[i].from,edge[i].to);
numm++;
}
if(numm==(m-2*n))
break;
}
}
return 0;
}
方法二:DFS或BFS
其实就是正向搜一遍,反向搜一遍。据某大佬说是因为正边保证根能到所有其他点,反边的那颗树保证所有其他点能到根。但我一直不明白为什么要加两次边QAQ...
后来听说因为前向星这个数据结构很特殊,只记录一个点的所有出边,没法记录指向一个点的所有边,所以要再加一个edge2,记录所有入边。这样其实正向反向边都是原图中的边。我还是有点懵懵的QAQ...我真的太菜了55555...
附上AC代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
using namespace std;
#define ll long long
typedef pair<int,int>pp;
#define mkp make_pair
#define pb push_back
const int MAX=100010;
int n,m;
struct edge1
{
int to,next;
}e1[MAX];//出边
int head1[MAX],tot1;
struct edge2
{
int from,next;
}e2[MAX];//入边
int head2[MAX],tot2;
void init()
{
tot1=tot2=0;
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
}
void addedge(int u,int v)
{
e1[tot1].to=v;e1[tot1].next=head1[u];head1[u]=tot1++;//记录"出去"方向的边
e2[tot2].from=u;e2[tot2].next=head2[v];head2[v]=tot2++;//记录指向该点的边
}
bool vis[MAX];
bool used[MAX];
void dfs1(int u)//正向树
{
for(int i=head1[u];i!=-1;i=e1[i].next)
{
int v=e1[i].to;
if(!vis[v])
{
vis[v]=true;
used[i]=true;
dfs1(v);
}
}
}
void dfs2(int v)//反向树
{
for(int i=head2[v];i!=-1;i=e2[i].next)
{
int u=e2[i].from;
if(!vis[u])
{
vis[u]=true;
used[i]=true;
dfs2(u);
}
}
}
int x[MAX],y[MAX];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
memset(used,false,sizeof(used));
init();
for(int i=0;i<m;i++)
{
scanf("%d%d",&x[i],&y[i]);
addedge(x[i],y[i]);
}
memset(vis,false,sizeof(vis));
vis[1]=true;
dfs1(1);
memset(vis,false,sizeof(vis));
vis[1]=true;
dfs2(1);
int numm=0;
for(int i=0;i<m;i++)
{
if(!used[i])
{
printf("%d %d\n",x[i],y[i]);
numm++;
}
if(numm==(m-2*n))
break;
}
}
return 0;
}