考强连通缩点,算模板题吧,比赛的时候又想多了,大概是不自信吧,才开始认真搞图论,把题目想复杂了。
题意就是给你任意图,保证是simple directed graph,问最多加多少条边能使图仍然是simple directed graph,即 无重边且整个图非强连通。
容易想到把所有的点分成两个集合,只要在同一个方向上把所有边都连上就很理想。那么点该如何分配呢?差值尽可能的大,因为总的边数不单单是两集合之间的边,还要算上集合内部全部的边,注意集合内部是在保证不出现重边的条件下的所有的边。
令总点数为n,一个集合的点数为k,则两个集合内的边数分别为 k*(k-1),(n-k)*(n-k-1)条,而两集合之间的边共有 k*(n-k)条,答案就是三个值相加再减去已有的m条边。
注意:虽然最理想的是一个集合里只有一个点,但实际是一个强连通的最小点集,见最后一组样例,而且可能都在一棵树上,所以只要缩点后找到出度或入度为0的分量中点数最小的就可以了。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std; const int MAXN=; struct Edge{
int v,next;
int vis;
Edge(){}
Edge(int _v,int _next):v(_v),next(_next),vis(){}
}edge[MAXN]; int head[MAXN],tol;
int stk[MAXN],dfn[MAXN],low[MAXN],top,TT;
int sub[MAXN],scc,num[MAXN]; int a[MAXN],b[MAXN];
int in[MAXN],out[MAXN]; void add(int u,int v)
{
edge[tol]=Edge(v,head[u]);
head[u]=tol++;
} void tarjan(int u)
{
int v;
dfn[u]=low[u]=++TT;
stk[top++]=u;
for(int i=head[u];i!=-;i=edge[i].next)
{
v=edge[i].v;
if(edge[i].vis)
continue;
edge[i].vis=;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(!sub[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
scc++;
int s=;
do{
v=stk[--top];
sub[v]=scc;
s++;
}while(v!=u);
num[scc]=s;
}
} void init()
{
tol=;
memset(head,-,sizeof(head)); memset(dfn,,sizeof(dfn));
memset(low,,sizeof(low));
memset(sub,,sizeof(sub));
} int main()
{
int T,n,m;
scanf("%d",&T);
for(int K=;K<=T;K++)
{
scanf("%d%d",&n,&m); init();
for(int i=;i<m;i++)
{
scanf("%d%d",&a[i],&b[i]);
add(a[i],b[i]);
} TT=;top=;scc=;
for(int i=;i<=n;i++)
if(!dfn[i])
tarjan(i); if(scc==){
printf("Case %d: -1\n",K);
continue;
} memset(in,,sizeof(in));
memset(out,,sizeof(out));
for(int i=;i<m;i++)
{
if(sub[a[i]]!=sub[b[i]]){
out[sub[a[i]]]++;
in[sub[b[i]]]++;
}
}
int min=;
for(int i=;i<=scc;i++)
{
if(!in[i]||!out[i])
if(num[min]>num[i])
min=i;
}
int k=num[min];
printf("Case %d: %d\n",K,k*(k-)+(n-k)*(n-k-)+k*(n-k)-m); }
return ;
}