HDU 4635 Strongly connected(强连通分量,变形)

时间:2022-06-29 03:40:46

题意:给出一个有向图(不一定连通),问最多可添加多少条边而该图仍然没有强连通。

思路:

  强连通分量必须先求出,每个强连通分量包含有几个点也需要知道,每个点只会属于1个强连通分量。

  在使图不强连通的前提下,要添加尽可能多的边。边至多有n*(n-1)条,而已经给了m条,那么所能添加的边数不可能超过k=n*(n-1)-m。

  这k条边还有部分不能添加,一添加立刻就强连通。一个强连通图最少只需要n条边,根据强连通的特性,缩点之后必定是不会有环的存在的,那么只要继续保持没有环的存在即可。我们只要让其中1个强连通分量没有出度,或者没有入度就行了。到底选哪个强连通分量?选点数少的,这点在后面会了解。因为1*10=10,而2*9=18啊,也就是两个数越接近,两者之积越大。

  如果挑出1个出度(入度也行的,同理)为0的强连通分量,该强连通分量的点数为d,那么答案就是k-d*(n-d),即不允许有其他n-d个点有任何一条边指向此强连通分量,其他边都是可以存在的。那么要使得k-d*(n-d)最大,那么d*(n-d)要最小,就是上面讲的要选哪个强连通分量的原因。

  那么应该找到缩点后出/入度为0的且包含点数最少的强连通分量来计算即可。

 #include <bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
using namespace std;
const int N=+;
const int INF=0x7f7f7f7f;
vector<int> vect[N];
stack<int> stac;
int num[N], chu[N], ru[N]; //统计强连通分量中点的个数
int scc_no[N], lowlink[N], dfn[N], dfn_clock, scc_cnt; //SCC必备
int n, m;
unordered_map<int,int> mapp;
void DFS(int x) //常规tarjan
{
stac.push(x);
dfn[x]=lowlink[x]=++dfn_clock;
for(int i=; i<vect[x].size(); i++)
{
int t=vect[x][i];
if(!dfn[t])
{
DFS(t);
lowlink[x]=min(lowlink[x],lowlink[t]);
}
else if(!scc_no[t]) //如果t不属于任何一个强连通分量
lowlink[x]=min(lowlink[x],dfn[t]);
}
if(lowlink[x]==dfn[x])
{
scc_cnt++;
while(true)
{
int t=stac.top();
stac.pop();
scc_no[t]=scc_cnt;
//scc[scc_cnt].push_back(t);
if(t==x) break;
}
}
} LL cal()
{
memset(dfn,,sizeof(dfn));
memset(lowlink,,sizeof(lowlink));
memset(scc_no,,sizeof(scc_no)); dfn_clock=scc_cnt=;
for(int i=; i<=n; i++) if(!dfn[i]) DFS(i); //深搜
if(scc_cnt==) return -; //已经强连通 memset(chu,,sizeof(chu));
memset(ru,,sizeof(ru));
for(int i=; i<=n; i++) //统计出入度数量
for(int j=; j<vect[i].size(); j++)
if(scc_no[i]!=scc_no[vect[i][j]] ) chu[scc_no[i]]=ru[scc_no[vect[i][j]]]=true; memset(num,,sizeof(num));
for(int i=; i<=n; i++) num[scc_no[i]]++; //统计每个scc中的点的数目
int small=INF;
for(int i=; i<=scc_cnt; i++) if(!chu[i]||!ru[i]) small=min(small,num[i]);//挑量小的,只要满足出/入度为0。如果都已经有出和入度了,那还计算啥!
return (LL)n*(n-) - m - small*(n-small); //注意10万*10万
} int main()
{
freopen("input.txt", "r", stdin);
int a, b, t, j=;
cin>>t;
while(t--)
{
scanf("%d%d",&n,&m);
mapp.clear();
int up=;
for(int i=; i<=n; i++) vect[i].clear();
for(int i=; i<m; i++)
{
scanf("%d%d", &a, &b);
if(!mapp[a]) mapp[a]=++up; //只是怕点号不连续,好像多余了
if(!mapp[b]) mapp[b]=++up;
vect[mapp[a]].push_back(mapp[b]);
}
printf("Case %d: %lld\n",++j,cal());
}
return ;
}

AC代码