HDU 4635 Strongly connected(强连通分量)

探讨如何在保持图非强连通的前提下,通过添加尽可能多的边来实现边数的最大化。介绍了一种算法思路,即通过寻找图中的强连通分量并进行缩点处理,进而确定最优解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:给你一个n个点和m条边的有向图,问你最多添加多少条边能使得该图依然不是强连通的?(若该图初始已经强连通,输出-1)

思路:这道题比较特别,是问最多添加多少条边使得这个图依然不是强连通的,显然正着想有点困难,如果反着想就简单多了,反过来其实就是问有一个强连通图,问你最少去掉多少条边使得这个图不是强连通。我们知道一个完全图有n*(n-1)条边,题目给出了m条,这时把各个强连通分量缩点,对于缩点之后的图分成两部分,使得这两部分不是强连通的,再用n(n-1)-m减去两部分能构造的最小边数,那么整体就是最大的了,显然这就是答案了。

          具体操作就是记录每个强连通块的包含的点个数num[i],枚举num[i](n-num[i])取最小,然后相减即可。有一点需要注意的是只有入度和出度为0的强连通块才能被选择。


另外一个思路:转自网上

        一个不能再添任何边的极大非强连通图一定满足下面情况:

        该图由x和y两部分构成,其中x是一个完全有向图,y也是一个完全有向图.且图中只有从x的任一节点到y中任一节点的边,并不存在从y到x的任何边. 最后x+y=n.

        上面的极大非连通图的边数F=x*(x-1)+y*(y-1)+x*y=n*(n-1)-x*y.我们要让F值最大,必须让x*y最小,由于x+y=n,所以当x与y的差值最大时,x*y必然最小,F值必然最大.用F-m即我们要求的结果.(仔细想想)

        那么x部分的点个数到底是多少呢?我们求出原图的所有强连通分量,然后缩点得新DAG图,只有那些入度==0或出度==0的点(所代表的分量)有资格成为x或y部分(仔细想想).我们只需找出最小值即可.


#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <map>
#include <string>
#include <set>
#include <ctime>
#include <cmath>
#include <cctype>
#include <stack>
using namespace std;
#define maxn 100000+100
#define LL long long
int cas=1,T;
vector<int>G[maxn];
int pre[maxn];
int lowlink[maxn];
int sccno[maxn];
int num[maxn];            //在i编号scc中有多少个点
int dfs_clock,scc_cnt;
int n,m;
stack<int>S;
void dfs(int u)
{
	pre[u]=lowlink[u]=++dfs_clock;
	S.push(u);
	for (int i = 0;i<G[u].size();i++)
	{
		int v = G[u][i];
		if (!pre[v])
		{
			dfs(v);
			lowlink[u] = min(lowlink[u],lowlink[v]);
		}
		else if (!sccno[v])
		{
			lowlink[u] = min (lowlink[u],pre[v]);
		}
	}
	if (lowlink[u] == pre[u])
	{
		scc_cnt++;
		for (;;)
		{
			int x = S.top();S.pop();
			sccno[x] = scc_cnt;
			num[scc_cnt]++;
			if (x==u)
				break;
		}
	}
}

void find_scc(int n)
{
	dfs_clock=scc_cnt=0;
	memset(sccno,0,sizeof(sccno));
	memset(pre,0,sizeof(pre));
	for (int i = 1;i<=n;i++)
		if (!pre[i])
			dfs(i);
}
int in[maxn];
int out[maxn];
int main()
{
	//freopen("in","r",stdin);
	scanf("%d",&T);
	while (T--)
	{
		printf("Case %d: ",cas++);
		scanf("%d%d",&n,&m);
		for (int i = 0;i<=n;i++)
			G[i].clear();
		memset(out,0,sizeof(out));
		memset(in,0,sizeof(in));
		memset(num,0,sizeof(num));
		for (int i = 1;i<=m;i++)
		{
			int u,v;
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
		}
		find_scc(n);
		for (int i = 1;i<=scc_cnt;i++)
		{
            out[i]=0;
			in[i]=0;
		}
		for (int u = 1;u<=n;u++)
			for (int i =0;i<G[u].size();i++)
			{
				int v = G[u][i];
				if (sccno[u] != sccno[v])
				{
					out[sccno[u]]++;
					in[sccno[v]]++;
				}
			}
	   if (scc_cnt == 1)
	   {
		   printf("-1\n");
           continue;
	   } 
		LL ans = n*(n-1)-m;
		int temp = 1<<30;
		for (int i = 1;i<=scc_cnt;i++)
		{
			if (in[i]==0 || out[i]==0)
                temp = min(temp,num[i]*(n-num[i]));
		}
		printf("%lld\n",ans-temp);
	}
	return 0;
}

Description

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. 
 

Input

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.
 

Output

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.
 

Sample Input

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
 

Sample Output

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值