Strongly connected(HDU 4635)---至多加边不成强连通分量

题目链接

题目描述

Give a simple directed graph with N nodes and M edges. Please tell me the maximum number of the edges you can add that the graph is still a simple directed graph. Also, after you add these edges, this graph must NOT be strongly connected.
A simple directed graph is a directed graph having no multiple edges or graph loops.
A strongly connected digraph is a directed graph in which it is possible to reach any node starting from any other node by traversing edges in the direction(s) in which they point.

输入格式

The first line of date is an integer T, which is the number of the text cases.
Then T cases follow, each case starts of two numbers N and M, 1<=N<=100000, 1<=M<=100000, representing the number of nodes and the number of edges, then M lines follow. Each line contains two integers x and y, means that there is a edge from x to y.

输出格式

For each case, you should output the maximum number of the edges you can add.
If the original graph is strongly connected, just output -1.

输入样例

3
3 3
1 2
2 3
3 1
3 3
1 2
2 3
1 3
6 6
1 2
2 3
3 1
4 5
5 6
6 4

输出样例

Case 1: -1
Case 2: 1
Case 3: 15

分析

题目大意是给定一个简单有向图,求至多加几条边使得这个图仍然是一个简单图且非强连通,若这个图恰好是强连通图则输出-1.
先说结论:将图缩点后,取点集最小的强连通分量,它的点集大小为x,那么ans=n(n-1)-x(n-x)-m.**
对于这道题我们这样考虑:

  • 首先对于两个强连通子图x和y,存在有向边从x的任意节点到y的任意节点,不存在有向边从y的任意节点到x的任意节点,即缩点后图是x->y。我们用|x|和|y|分别表示两个强连通图点集的大小,且|x|+|y|=n。
  • 对于这样一个图所能构成的最大非强连通图G,G的边数应为F=|x|(|x|-1)+|y|(|y|-1)+|x||y|=n*(n-1)-x*y,即强连通子图x和y构成完全子图,且x中任意节点都有指向y任意节点的边。对于这样的简单图,如果再加一条边即从y的任意节点指向x的任意节点,就必会构成强连通图。

因此,我们可以将这个情况推广至多个强连通子图的情况:

  • 设G是一个简单图,对G进行缩点后,G中所包含的强连通子图有k个,分别是G1,G2,G3,…,Gk。考虑到要使的F-m最大,即F=n*(n-1)-xy最大,那么就必须使得xy最小,接下来就是证明取最小点集时x*y最小。
  • 在这k个子图中任取两个Gi和Gj,设他们大小分别为p、q且p>q,那么有x1y1=p(n-p),x2y2=q(n-q),做差得x1y1-x2y2=(p-q)(n-(p+q)),由于(p-q)>0,(n-(p+q))>0,故x1y1>x2y2,显然此时应取图Gj,所以取强连通子图点集最小的图时F最小。

综上,将图缩点后,取点集最小的强连通分量,它的点集大小为x,那么ans=n(n-1)-x(n-x)-m.**

源程序

tarjan算法

#include <bits/stdc++.h>
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
struct Edge{
	int v,next;
	Edge(){};
	Edge(int _v,int _next){
		v=_v,next=_next;
	}
}edge[MAXN];
stack<int> s; 
int EdgeCount,head[MAXN];
int t,n,m,cnt,color;
int dfn[MAXN],low[MAXN],num[MAXN],sum[MAXN];
bool vis[MAXN],in[MAXN],out[MAXN];
void addEdge(int u,int v)
{
	edge[++EdgeCount]=Edge(v,head[u]);
	head[u]=EdgeCount;
}
void init()	//初始化 
{
	for(int i=1;i<=n;i++){
		head[i]=dfn[i]=low[i]=num[i]=sum[i]=0;
		vis[i]=in[i]=out[i]=false;
	}
	while(!s.empty())s.pop();
	EdgeCount=cnt=color=0;
}
int  read()	//快读 
{
	int sum=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){
		sum=sum*10+c-'0';
		c=getchar();
	}
	return sum;
}
void Tarjan(int u)
{
	dfn[u]=low[u]=++cnt;	//时间戳 
	vis[u]=true;	//标记入栈 
	s.push(u);
	for(int i=head[u];i;i=edge[i].next){
		int v=edge[i].v;
		if(!dfn[v]){	//还没被盖上时间戳 
			Tarjan(v);
			low[u]=min(low[u],low[v]); 
		}
		else if(vis[v])	//盖已盖上时间戳但还在栈中 
			low[u]=min(low[u],dfn[v]);	//这里必须用dfn更新
	}									//否则在处理割点问题时会漏判 
	if(dfn[u]==low[u]){	//满足强连通分量 
		color++;
		while(1){
			int tmp=s.top();s.pop();
			num[tmp]=color;	//标记强连通分量
			sum[color]++;	//该强连通分量点个数
			vis[tmp]=false; //出栈
			if(tmp==u)break; 
		}
	}
}
int main()
{	
	t=read();
	for(int cas=1;cas<=t;cas++){
		n=read(),m=read();
		init(); 
		for(int i=1;i<=m;i++){
			int u=read(),v=read();
			addEdge(u,v);
		}
		for(int i=1;i<=n;i++)	//缩点 
			if(!dfn[i])
				Tarjan(i);
		for(int u=1;u<=n;u++){	//统计缩点后出入度 
			for(int i=head[u];i;i=edge[i].next){
				int v=edge[i].v;
				if(num[u]!=num[v]){	//不在一个强连通分量内
					out[num[u]]=true;
					in[num[v]]=true; 
				}
			} 
		}
		ll ans=0;
		int minn=INF;
		for(int i=1;i<=color;i++)	//计算最小个数的强连通分量 
			if(!in[i]||!out[i])
				minn=min(minn,sum[i]);
		if(color>1)ans=(ll)n*n-n-(ll)minn*(n-minn)-m;
		else ans=-1;	//恰好为强连通分量
		printf("Case %d: ",cas);
		printf("%d\n",ans); 
	}
}

Kosaraju算法

#include <bits/stdc++.h>
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
struct Edge{
	int v,next;
	Edge(){}
	Edge(int v,int next):v(v),next(next){}
};
struct GNode{
	Edge edge[MAXN];
	int EdgeCount,head[MAXN];
	void clear(){
		memset(head,0,sizeof(head));
		EdgeCount=0;
	}
	void addEdge(int u,int v){
		edge[++EdgeCount]=Edge(v,head[u]);
		head[u]=EdgeCount;
	}
};
GNode G,GT;		//原图、反图 
int t,n,m,cnt,color;
int dfn[MAXN],low[MAXN],num[MAXN],sum[MAXN];
bool vis[MAXN],in[MAXN],out[MAXN];
void init()
{
	for(int i=1;i<=n;i++){
		dfn[i]=low[i]=num[i]=sum[i]=0;
		in[i]=out[i]=false;
	}
	G.clear();GT.clear();
	cnt=color=0;
}
int read()		//快读 
{
	int sum=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){
		sum=sum*10+c-'0';
		c=getchar();
	}
	return sum;
}
void dfs1(int u)	//第一次深搜记录完成时间 
{
	vis[u]=true;
	for(int i=G.head[u];i;i=G.edge[i].next){
		int v=G.edge[i].v;
		if(!vis[v])	//还没访问过 
			dfs1(v);
	}
	dfn[++cnt]=u;
}
void dfs2(int u)	//第二次深搜标记强连通分量 
{
	vis[u]=true;
	num[u]=color;
	sum[color]++;
	for(int i=GT.head[u];i;i=GT.edge[i].next){
		int v=GT.edge[i].v;
		if(!vis[v])	//还未访问过 
			dfs2(v);
	}
}
void Kosaraju()
{
	/*第一次深搜*/
	memset(vis,false,sizeof(vis));
	for(int i=1;i<=n;i++)
		if(!vis[i])
			dfs1(i);
	
	/*第二次深搜*/
	memset(vis,false,sizeof(vis));
	for(int i=n;i>=1;i--)
		if(!vis[dfn[i]]){
			color++;
			dfs2(dfn[i]);
		}
}
int main()
{
	t=read();
	for(int cas=1;cas<=t;cas++){	
		n=read(),m=read();
		init();
		for(int i=1;i<=m;i++){	//建图 
			int u=read(),v=read();
			G.addEdge(u,v);
			GT.addEdge(v,u);
		}
		Kosaraju();
		for(int u=1;u<=n;u++){	//统计各点出入度 
			for(int i=G.head[u];i;i=G.edge[i].next){
				int v=G.edge[i].v;
				if(num[u]!=num[v]){
					out[num[u]]=true;
					in[num[v]]=true; 
				}
			}
		}
		ll ans=0;
		int minn=INF;
		for(int i=1;i<=color;i++)	//计算最小个数的强连通分量 
			if(!in[i]||!out[i])
				minn=min(minn,sum[i]);
		if(color>1)ans=(ll)n*n-n-(ll)minn*(n-minn)-m;
		else ans=-1;	//恰好为强连通分量
		printf("Case %d: ",cas);
		printf("%d\n",ans); 
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值