题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4635
题目大意:
给定一个有向图,
问至多加多少条边,
使得新图是非强连通的简单图。
算法:
与其算加多少边,不如算有多少边不能加。
很容易想到,最终的图是分成两个强连通分支的。
这两个强联通分支内部分别是完全图,强连通分支之间的边都是由某个SCC指向另一个SCC的,不可能两个方向都有。
所以,不能加的就是两个SCC之间另一个方向上的边,数量就是两个SCC中点数的乘积。
不难看出让两个SCC的点数差距越大越好。
那么首先进行SCC缩点。
缩点后,如果一个SCC出度入度均不为0,那么它最终肯定是不可能单独作为剩下的两个SCC之一了,因为最后两个SCC间的边都是沿同一方向的。
在所有出度或入度为0的SCC中,我们找一个点数最少的。
那么最终就是这个SCC在一边,其它所有的点在另一边,中间的边都是沿着同一方向(出或入)。
代码如下:
#include <iostream>
#include <cstring>
#include <stdio.h>
#include <math.h>
#include <queue>
#include <vector>
#include <algorithm>
#include <stack>
#include <map>
using namespace std;
#define ll long long
#define inf 2e9
#define pii pair<int,int>
#define fr first
#define sc second
const int MAXN=110000;
stack<int> ss;
int dep[MAXN], low[MAXN], cot[MAXN];
int dex1[MAXN],dex2[MAXN],blk[MAXN];
vector<int> mm[MAXN];
void tarjan(int u, int p)
{
int tmp=low[u]=dep[u]=(p==-1)?0:dep[p]+1;
ss.push(u);
for(int i=0; i<mm[u].size(); i++)
{
int v=mm[u][i];
if(dep[v]==-1)
{
tarjan(v,u);
}
tmp=min(tmp,low[v]);
}
low[u]=tmp;
if(low[u]==dep[u])
{
while(ss.top()!=u)
{
cot[u]++;
blk[ss.top()]=u;
ss.pop();
}
blk[ss.top()]=u;
ss.pop();
cot[u]++;
}
}
int main(){
int cas;
scanf("%d", &cas);
for(int T=1; T<=cas; T++)
{
int n, m;
scanf("%d%d", &n, &m);
memset(dep, -1, sizeof(dep));
memset(cot, 0, sizeof(cot));
memset(dex1, 0, sizeof(dex1));
memset(dex2, 0, sizeof(dex2));
while(!ss.empty())
{
ss.pop();
}
for(int i = 0; i < n; i ++)
{
mm[i].clear();
}
for(int i = 0; i < m; i ++)
{
int u,v;
scanf("%d%d", &u, &v);
u--;
v--;
mm[u].push_back(v);
}
for(int i=0; i<n; i++)
{
if(dep[i]==-1)
{
tarjan(i,-1);
}
}
for(int u=0; u<n; u++)
{
for(int i=0; i<mm[u].size(); i++)
{
int v=mm[u][i];
if(blk[u]==blk[v])
{
continue;
}
dex1[blk[u]]++;
dex2[blk[v]]++;
}
}
long long ans=-1;
for(int i=0; i<n; i++)
{
if(cot[i]==0||cot[i]==n)
{
continue;
}
if(dex1[i]&&dex2[i])
{
continue;
}
ans=max(ans,(long long)n*(n-1)-(long long)cot[i]*(n-cot[i])-m);
}
printf("Case %d: %I64d\n",T,ans);
}
return 0;
}