HDU 4635 Strongly connected(Tarjian强连通分量)

21 篇文章 0 订阅
7 篇文章 0 订阅

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 的条件就是当前分量的出度或者入度为零。

因此做法为:

  1. Tarjina缩点,求出所有强连通分量的点数、出入度。
  2. 对于所有出度或者入度为零的分量代公式计算答案。
  3. 输出最大的答案。

公式:假设 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(a1)+b(b1)+abm

#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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值