HDU 4635 Strongly connected
题目
给你一个有向图,如果是强连通图,输出 -1,否则让你加一定的边并让图保持非强连通,输出能加的最多的边数。
分析
考虑有向图什么时候边数最多且非强联通。
直接给出结论:只有两个 S C C SCC SCC 分量 a,b,两个分量都是强连通图且其中一个中全部点指向另外一个全部点。
我们考虑直接构造这样的图,假设初始有 n n n 个 S C C SCC SCC 分量,要选择其中一个分量作为 a 或者 b,图的剩下部分作为一个 a 或者 b,而能做为 a 或 b 的条件就是当前分量的出度或者入度为零。
因此做法为:
- Tarjina缩点,求出所有强连通分量的点数、出入度。
- 对于所有出度或者入度为零的分量代公式计算答案。
- 输出最大的答案。
公式:假设 a 指向 b,且两个分量点数为 a, b
a n s = a ∗ ( a − 1 ) + b ∗ ( b − 1 ) + a ∗ b − m ans = a*(a-1) + b*(b-1) + a * b - m ans=a∗(a−1)+b∗(b−1)+a∗b−m
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define ll long long
#define fuck(x) cout<<x<<endl
const int N = 1e5 + 5;
const ll mod = 998244353;
int n, m, t;
vector<int> g[N];
int in[N], out[N];
int pre[N], low[N], sccno[N], num[N], dfs_clock, scc_cnt;
stack<int> s;
void dfs(int u){
pre[u] = low[u] = ++dfs_clock; // 标记每个点访问时间
s.push(u);
for (int i = 0; i < g[u].size(); i++){ // 遍历 u 所有子节点
int v = g[u][i];
if(!pre[v]){
dfs(v);
low[u] = min(low[u], low[v]); // 用子节点的 low 值更新 u 的 low 值。
}else if(!sccno[v]) // 如果 v 不属于 SCC
low[u] = min(low[u], pre[v]); // 反向边更新 u 的 low 值
}
if(low[u] == pre[u]){ // 如果 u 及其后代最早能达到的祖先只能到 u
scc_cnt++; // u 就是 SCC 访问第一个点
while(1){
int x = s.top();
s.pop();
sccno[x] = scc_cnt;
num[scc_cnt]++;
if(x == u)
break;
}
}
}
void find_scc(int n){ // Tarjina 缩点模板
dfs_clock = scc_cnt = 0;
memset(sccno, 0, sizeof sccno);
memset(pre, 0, sizeof pre);
memset(low, 0, sizeof(low));
memset(num, 0, sizeof(num));
for(int i = 1; i <= n; i++){
if(!pre[i])
dfs(i);
}
}
ll cal(ll x){
return x * (x - 1) + (n - x) * (n - x - 1) + x * (n - x) - m;
}
int main(){
scanf("%d", &t);
for(int cas = 1; cas <= t; cas++){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
g[i].clear();
}
for (int i = 0, u, v; i < m; i++){
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
find_scc(n);
// fuck(scc_cnt);
printf("Case %d: ", cas);
if(scc_cnt == 1){
printf("-1\n");
continue;
}
for(int i = 1; i <= scc_cnt; i++) // 求所有分量出入度
in[i] = out[i] = 0;
for(int u = 1; u <= n; u++){
for(int i = 0; i < g[u].size(); i++){
int v = g[u][i];
if(sccno[u] != sccno[v])
in[sccno[v]] = out[sccno[u]] = 1;
}
}
ll ans = 0; // 找出最大的答案
for(int i = 1; i <= scc_cnt; i++){
if(!in[i] || !out[i]){
// fuck("fsd");
ans = max(ans, cal(num[i]));
}
}
printf("%lld\n", ans);
}
}