https://vjudge.net/problem/HDU-4635#author=634579757
有t组数据,给了一个n个点m条边的有向图,问给此图最多加多少条边,满足如下条件:
1:此图不能强连通
2:没有重边和自环
3:当图本身就是强连通的时候就输出-1
那么现在我们考虑,如何加边使得加的边尽可能的多,又不强连通. 在使图不强连通的前提下,要添加尽可能多的边。边至多有n∗(n−1)条,而已经给了m条,那么所能添加的边数不可能超过k=n∗(n−1)−m。
这k条边还有部分不能添加,一添加立刻就强连通。一个强连通图最少只需要n条边,根据强连通的特性,缩点之后必定是不会有环的存在的,那么只要继续保持没有环的存在即可。我们只要让其中1个强连通分量没有出度,或者没有入度就行了。到底选哪个强连通分量?选点数少的,这点在后面会了解。
例如1×10=10,2×9=18,故选点数越小的SCC越好
如果挑出1个出度(入度也行的,同理)为0的强连通分量,该强连通分量的点数为d,那么答案就是k-d(n-d),即不允许有其他n-d个点有任何一条边指向此强连通分量,其他边都是可以存在的。那么要使得k-d(n-d)最大,那么d(n-d)要最小,就是上面讲的要选哪个强连通分量的原因。
那么应该找到缩点后出/入度为0的且包含点数最少的强连通分量来计算即可。
#include<iostream> #include<cstring> #include<cstdio> #define ll long long using namespace std; const int N=1e5+5; int ver[N],Next[N],head[N],dfn[N],low[N]; int stack[N],ins[N],in[N],out[N],scc_cnt[N],c[N]; int n,m,tot,num,top,cnt,t,count=1,minv; void add(int x,int y){ ver[++tot]=y,Next[tot]=head[x],head[x]=tot; } void tarjan(int x){ dfn[x]=low[x]=++num; stack[++top]=x,ins[x]=1; for(int i=head[x];i;i=Next[i]) if(!dfn[ver[i]]){ tarjan(ver[i]); low[x]=min(low[x],low[ver[i]]); } else if(ins[ver[i]]) low[x]=min(low[x],dfn[ver[i]]); if(dfn[x]==low[x]){ cnt++;int y; do{ y=stack[top--],ins[y]=0; c[y]=cnt,scc_cnt[cnt]++; }while(x!=y); } } void init(){ minv=1e9;cnt=0;top=0;tot=0; memset(head,0,sizeof head); memset(dfn,0,sizeof dfn); memset(low,0,sizeof low); memset(scc_cnt,0,sizeof scc_cnt); memset(in,0,sizeof in); memset(out,0,sizeof out); memset(c,0,sizeof c); } int main(){ scanf("%d",&t); while(t--){ init(); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); add(x,y); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); for(int x=1;x<=n;x++) for(int i=head[x];i;i=Next[i]){ int y=ver[i]; if(c[x]==c[y]) continue; in[c[y]]++,out[c[x]]++; } for(int i=1;i<=cnt;i++) if(!in[i]||!out[i]) minv=min(minv,scc_cnt[i]); if(cnt==1) printf("Case %d: -1\n",count++); else printf("Case %d: %lld\n",count++,(ll)n*(n-1)-(ll)m-(ll)minv*(n-minv)); } return 0; }