题目描述
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);
}
}