题目链接:点击查看
题目大意:给出一张 n 个点 m 条边的无向图,现在需要将这张图转换为有向图,并且使得 k 个可达条件成立,输出一种构造方案
题目分析:如果在无向图中出现环的话,那么在转换为有向图后,环上的点一定是可以使得互相可达的,所以我们考虑 tarjan 边双缩点,将整个图缩成一棵树,在缩边的时候,只需要在 dfs 树上一直加边就可以构造环了
现在只需要考虑缩边后的树边方向即可,对于一个可达条件的限制 ( x , y ) ,设是需要从 x -> y,因为在一棵树上路径唯一,我们先求出 lca = LCA( x , y ) ,维护两个数组 in 和 out ,其意义分别是:
- in[ i ] :有 in[ i ] 条边的方向需要从 fa[ i ] -> i
- out[ i ] :有 out[ i ] 条边的方向需要从 i -> fa[ i ]
对于一条边 ( x , y ) ,可以用树链剖分,分别维护 x -> lca 和 lca -> y 这两段的边上的 in 数组和 out 数组,最后对于某条边来说,如果 in[ i ] 和 out[ i ] 皆不为 0 的话,那么显然是无解的,否则根据上面的两种情况确定一下
不过马哥有个很棒的想法,就是用树上差分来代替树链剖分,对于一个要求 ( x , y ) 来说,只需要让 out[ x ] ++ , out[ lca ] -- , in[ y ] ++ , in[ lca ] -- ,最后一遍 dfs 统计一下树上前缀和就好了,时空复杂度以及代码难度相对树链剖分来说都优秀太多了
剩下的就是实现了,耐心码就好了,没什么坑点
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=1e4+100;
const int M=2e5+100;
set<pair<int,int>>ans,st;
struct Egde
{
int to,next;
}edge1[M],edge2[M];
int head1[N],head2[N],low[N],dfn[N],c[N],out[N],in[N],num,cnt1,cnt2,dcc,n,m;
bool bridge[M],vis[M];
void addedge1(int u,int v)
{
edge1[cnt1].to=v;
edge1[cnt1].next=head1[u];
head1[u]=cnt1++;
}
void addedge2(int u,int v)
{
edge2[cnt2].to=v;
edge2[cnt2].next=head2[u];
head2[u]=cnt2++;
}
void tarjan(int u,int in_edge)
{
dfn[u]=low[u]=++num;
for(int i=head1[u];i!=-1;i=edge1[i].next)
{
int v=edge1[i].to;
if(!dfn[v])
{
tarjan(v,i);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
bridge[i]=bridge[i^1]=true;
}
else if(i!=(in_edge^1))
low[u]=min(low[u],dfn[v]);
}
}
void dfs(int u)
{
c[u]=dcc;
for(int i=head1[u];i!=-1;i=edge1[i].next)
{
int v=edge1[i].to;
if(bridge[i])
continue;
if(!vis[i]&&!vis[i^1])
{
ans.insert(make_pair(u,v));
vis[i]=vis[i^1]=true;
}
if(c[v])
continue;
dfs(v);
}
}
void solve()
{
for(int i=1;i<=n;i++)//找桥
if(!dfn[i])
tarjan(i,0);
for(int i=1;i<=n;i++)//缩点
if(!c[i])
{
dcc++;
dfs(i);
}
}
void build()//缩点+连边
{
solve();
for(int i=2;i<cnt1;i+=2)
{
int u=edge1[i^1].to;
int v=edge1[i].to;
if(c[u]==c[v])
continue;
addedge2(c[u],c[v]);
addedge2(c[v],c[u]);
}
}
int deep[N],dp[N][20],limit;
void dfs1(int u,int fa,int dep)//树上倍增
{
deep[u]=dep;
dp[u][0]=fa;
for(int i=1;i<=limit;i++)
dp[u][i]=dp[dp[u][i-1]][i-1];
for(int i=head2[u];i!=-1;i=edge2[i].next)
{
int v=edge2[i].to;
if(v!=fa)
dfs1(v,u,dep+1);
}
}
int LCA(int x,int y)
{
if(deep[x]<deep[y])
swap(x,y);
for(int i=limit;i>=0;i--)
if(deep[x]-deep[y]>=(1<<i))
x=dp[x][i];
if(x==y)
return x;
for(int i=limit;i>=0;i--)
if(dp[x][i]!=dp[y][i])
{
x=dp[x][i];
y=dp[y][i];
}
return dp[x][0];
}
void dfs2(int u,int fa)//统计树上差分的前缀和
{
for(int i=head2[u];i!=-1;i=edge2[i].next)
{
int v=edge2[i].to;
if(v==fa)
continue;
dfs2(v,u);
in[u]+=in[v];
out[u]+=out[v];
}
}
void init()
{
ans.clear();
limit=log2(n)+1;
cnt1=2;
cnt2=num=dcc=0;
memset(head2,-1,sizeof(head2));
memset(head1,-1,sizeof(head1));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(bridge,false,sizeof(bridge));
memset(c,0,sizeof(c));
memset(out,false,sizeof(out));
memset(in,false,sizeof(in));
memset(vis,false,sizeof(vis));
}
int main()
{
#ifndef ONLINE_JUDGE
// freopen("data.in.txt","r",stdin);
// freopen("data.out.txt","w",stdout);
#endif
// ios::sync_with_stdio(false);
scanf("%d%d",&n,&m);
init();
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
addedge1(u,v);
addedge1(v,u);
}
build();
dfs1(1,0,0);
int x,y;
int k;
scanf("%d",&k);
while(k--)
{
int x,y;
scanf("%d%d",&x,&y);
if(c[x]==c[y])//属于同一个连通分量
continue;
int lca=LCA(c[x],c[y]);//c[x]->c[y]
out[c[x]]++;
out[lca]--;
in[c[y]]++;
in[lca]--;
}
dfs2(1,-1);
bool flag=true;
for(int i=1;i<=dcc;i++)//枚举每个点与其父节点的关系
{
if(out[i]&&in[i])
{
flag=false;
break;
}
if(out[i])
st.insert(make_pair(i,dp[i][0]));
else
st.insert(make_pair(dp[i][0],i));
}
if(!flag)
return 0*puts("No");
for(int i=2;i<cnt1;i++)
{
int u=edge1[i].to,v=edge1[i^1].to;
if(c[u]==c[v])
continue;
if(st.count(make_pair(c[u],c[v])))
ans.insert(make_pair(u,v));
}
puts("Yes");
for(auto it:ans)
printf("%d %d\n",it.first,it.second);
return 0;
}