CF118E
作者:互联网
题意:
判断是否能把一个无向图的所有无向边改成有向边,使得改完后两两点可达,并给出方案。
思路:
可以糊出一个模糊的做法,就是先从图里深度优先遍历一条经过所有点且只经过一次的路径,然后就是有了一条链(请把它看作一个链,而不是生成树)。
那么剩下的边必然连到它的祖先或者直接儿子。
然后可以发现,不存在方案等价于存在一种深度优先遍历的方式使得存在一个点和它的子树内都没有返祖边。
也就是说,存在桥。
当然,如果知道了结论是桥的话,证明是很简单的,因为删了桥后图不再连通,那么定向后两块一定有一块总到不了另一块
Tarjan 判一发桥。
然后怎么改,由于已经保证没有桥,所以每个点的子树内都有返祖边,当然就是树上的边指向儿子,剩余的边指向祖先
为啥这是对的呢,假设根是 \(root\)。
那么很明显 \(root\) 能到达所有点,那么只需要证明所有点都能到 \(root\) 即可:
首先,必然存在一个 \(root\) 的度大于等于 \(2\),选他为正式的 \(root\)。
那么,肯定是一个边是 \(root\) 指向生成树树上的儿子,另一些是 \(u\) 指向 \(root\) 的(注意方向)。
所以 \(root\) 和 \(u\) 之间的所有点都能走到 \(root\) 了
由于所有在 \(u\) 的子树内一定存在 \(v\) 满足 \(v\) 和 \(u\) 的某个祖先有连边,否则就存在桥了。
然后能走到 \(root\) 就有传递性了
感性理解,不能走时就往下走到第一个有返祖边的点再往上跳。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,p[(int)(2e5+10)];
vector<int>e[(int)(2e5+10)];
int fa[(int)(2e5+10)];
int dfn[(int)(2e5+10)],low[(int)(2e5+10)],id=0,num=0;
bool f=0;
struct node
{
int u,v;
}a[(int)(3e5+10)],b[(int)(3e5+10)];
int top1;
map<pair<int,int>,int>is;
void dfs(int u)
{
dfn[u]=low[u]=++num;
for(int i=0;i<e[u].size();i++)
{
int v=e[u][i];
bool fl=0;
if(!dfn[v])
{
b[++top1]=(node){u,v};
is[make_pair(u,v)]=is[make_pair(u,v)]=1;
fa[v]=u;dfs(v);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
{
f=1;
}
}
else
{
if(fa[u]!=v)
{
low[u]=min(low[u],dfn[v]);
}
}
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v;cin>>u>>v;
e[u].push_back(v);e[v].push_back(u);
a[i]=(node){u,v};
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
dfs(i);
}
}
// for(int i=1;i<=n;i++)
// {
// cout<<dfn[i]<<"\n";
// }
// puts("");
if(f==1) puts("0");
else
{
for(int i=1;i<=top1;i++)
{
cout<<b[i].u<<" "<<b[i].v<<"\n";
}
for(int i=1;i<=m;i++)
{
if(is[make_pair(a[i].v,a[i].u)]||is[make_pair(a[i].u,a[i].v)]) continue;
if(dfn[a[i].v]<dfn[a[i].u]) cout<<a[i].u<<" "<<a[i].v<<"\n";
else cout<<a[i].v<<" "<<a[i].u<<"\n";
}
}
system("pause > null");
}
标签:10,CF118E,int,2e5,返祖,low,root 来源: https://www.cnblogs.com/blogbyWHY/p/15848248.html