题意:
n个点m条边的有向图,在保证不是强连通图的情况下最多可以添加多少条边。1 <= n <= 1e5 ,1 <= m <= 1e5。
题解:
1.正向思考不容易想到,逆向思考,把该有向图变成完全图,即n*(n-1)条边,在不减少原有的m条边内的边的条件下最多可以减少多少条边。最后得到一个有两个强连通分量的图,记作X和Y,X分量有x个点,Y分量有y个点,X分量是完全子图,Y分量是完全子图,X分量的每个点都有指向Y分量的每个点的边。可添加x*(x-1) + y*(y-1) + x*y - m = n*(n-1) - x * y - m条边。尽量使x * y小,即尽量使abs(x - y)大。
2.Tarjan缩点,记录每个强连通分量的点的个数。然后找到最小的分量点数num,答案为 n*(n-1) - num * (n - num) - m。
#include<bits/stdc++.h>
#define N 100005
#define inf 0x3f3f3f3f
using namespace std ;
struct Edge
{
int to , next ;
} edge[N] ;
int head[N] ;
int vis[N] , dfn[N] , low[N] , s[N] ;
int id[N] , in[N] , out[N] ;
long long scc[N] ;
int scc_num , bridge_num ;
int top1 , lay , cnt ;
long long n , m ;
void init()
{
top1 = lay = 0 ;
scc_num = bridge_num = 0 ;
cnt = 0 ;
memset(head , -1 , sizeof(head)) ;
memset(vis , 0 , sizeof(vis)) ;
memset(in , 0 , sizeof(in)) ;
memset(out , 0 , sizeof(out)) ;
memset(scc , 0 , sizeof(scc)) ;
}
void addedge(int u , int v)
{
edge[cnt].to = v ;
edge[cnt].next = head[u] ;
head[u] = cnt ++ ;
}
void Tarjan(int u)
{
int v ;
int i , j ;
lay ++ ;
vis[u] = 1 ;
low[u] = lay ;
dfn[u] = lay ;
s[++ top1] = u ;
for(i = head[u] ; i != -1 ; i = edge[i].next)
{
v = edge[i].to ;
if(vis[v] == 1)
low[u] = min(low[u] , dfn[v]) ;
if(vis[v] == 0)
{
Tarjan(v) ;
low[u] = min(low[u] , low[v]) ;
}
}
if(dfn[u] == low[u])
{
scc_num ++ ;
while(s[top1] != u)
{
scc[scc_num] ++ ;
id[s[top1]] = scc_num ;
vis[s[top1]] = 2 ;
top1 -- ;
}
scc[scc_num] ++ ;
id[s[top1]] = scc_num ;
vis[s[top1]] = 2 ;
top1 -- ;
}
}
void find()
{
int i , j ;
for(i = 1 ; i <= n ; i ++)
for(j = head[i] ; j != -1 ; j = edge[j].next)
{
if(id[i] != id[edge[j].to])
{
out[id[i]] ++ ;
in[id[edge[j].to]] ++ ;
}
}
}
long long cal()
{
int i ;
long long x , y ;
long long num = n ;
long long ans = 0 ;
for(i = 1 ; i <= scc_num ; i ++)
{
if(in[i] != 0 && out[i] != 0)
continue ;
num = min(num , scc[i]) ;
}
ans = n * (n - 1) - num * (n - num) - m ;
return ans ;
}
int main()
{
int t , case_num = 0 ;
int i , j ;
int u , v ;
long long ans ;
scanf("%d" , &t) ;
while(t --)
{
init() ;
scanf("%lld%lld" , &n , &m) ;
for(i = 1 ; i <= m ; i ++)
{
scanf("%d%d" , &u , &v) ;
addedge(u , v) ;
}
for(i = 1 ; i <= n ; i ++)
if(vis[i] == 0)
Tarjan(i) ; //缩点
find() ; // 找入度为0的点和出度为0的点
ans = cal() ; // 计算答案
if(scc_num == 1)
ans = -1 ;
printf("Case %d: %lld\n" , ++case_num , ans) ;
}
}