中石油训练赛 - One-Way Conveyors(边双缩点+树上差分)

中石油训练赛 - One-Way Conveyors(边双缩点+树上差分)_第1张图片

题目链接:点击查看

题目大意:给出一张 n 个点 m 条边的无向图,现在需要将这张图转换为有向图,并且使得 k 个可达条件成立,输出一种构造方案

题目分析:如果在无向图中出现环的话,那么在转换为有向图后,环上的点一定是可以使得互相可达的,所以我们考虑 tarjan 边双缩点,将整个图缩成一棵树,在缩边的时候,只需要在 dfs 树上一直加边就可以构造环了

现在只需要考虑缩边后的树边方向即可,对于一个可达条件的限制 ( x , y ) ,设是需要从 x -> y,因为在一棵树上路径唯一,我们先求出 lca = LCA( x , y ) ,维护两个数组 in 和 out ,其意义分别是:

  1. in[ i ] :有 in[ i ] 条边的方向需要从 fa[ i ] -> i
  2. 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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>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=0;i--)
		if(deep[x]-deep[y]>=(1<=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

 

你可能感兴趣的:(图论,树上差分)