码迷,mamicode.com
首页 > 其他好文 > 详细

「UVA 12167」 Proving Equivalences

时间:2020-05-05 11:04:31      阅读:61      评论:0      收藏:0      [点我收藏+]

标签:一个   size   ons   max   现在   break   string   bre   using   

Description

在数学中,我们常常需要完成若干命题的等价性证明。

例如:有 \(4\) 个命题 \(a,b,c,d\),要证明他们是等价的,我们需要证明 \(a\Leftrightarrow b\),然后 \(b\Leftrightarrow c\),最后 \(c\Leftrightarrow d\)。注意每次证明是双向的,因此一共完成了 \(6\) 次推导。另一种证明方法是:证明 \(a\rightarrow b\),然后 \(b\rightarrow c\),接着 \(c\rightarrow d\),最后 \(d\rightarrow a\),只须 \(4\) 次证明。

现在你任务是证明 \(n\) 个命题全部等价,且你的朋友已经为你作出了 \(m\) 次推导(已知每次推导的内容),你至少还需做几次推导才能完成整个证明。

Hint

  • \(1\le n\le 2\times 10^4\)
  • \(0\le m\le 5\times 10^4\)

Solution

实际上这是一个图论题:给定一个有向图,问至少需要加几条边,可以使整个图变为一个 强连通图

首先要知道,把边加到 同一个 \(\texttt{SCC}\) 中的某两点之间 没有任何意义,于是我们把做个图2做一个 缩点 的操作,使其成为一个 DAG。

然后我们就是在这些缩点后的点上加边。

显然一个 SCC 中,任意两点是互相可达的,于是所有点都必须有入度和出度。

若设入度为 \(0\) 的点集为 \(S\), 出度为 \(0\) 的点集为 \(T\),那么至少 \(|S| + |T|\) 条边必能构造处一个强连通图。

但我们可以“一边两用”:将尽可能多的边连成从 \(T\) 中的点到 \(S\) 中的点的边。这样答案就是 \(\max(|S|,|T|)\)

缩点用 \(\texttt{Tarjan}\) 算法,复杂度 \(O(n + m)\)

Code

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <stack>
#include <vector>

using namespace std;
const int N = 2e5 + 5;

vector<int> G[N];
int n, m, belong[N];

vector<int> Gx[N];
int scc, in[N], out[N];

int dfn[N], low[N];
int timer;
stack<int> st;
bool inst[N];

void Tarjan(int x) {
	dfn[x] = low[x] = ++timer;
	st.push(x), inst[x] = true;
	for (auto y : G[x])
		if (!dfn[y]) Tarjan(y), low[x] = min(low[x], low[y]);
		else if (inst[y]) low[x] = min(low[x], dfn[y]);
	
	if (dfn[x] == low[x])
		for (++scc; ; ) {
			int k = st.top(); st.pop();
			inst[k] = false, belong[k] = scc;
			if (x == k) break;
		}
}

void solve() {
	memset(dfn, 0, sizeof dfn);
	memset(low, 0, sizeof low);
	memset(inst, 0, sizeof inst);
	memset(in, 0, sizeof in);
	memset(out, 0, sizeof out);
	memset(belong, 0, sizeof belong);
	timer = scc = 0, st = stack<int>();
	
	scanf("%d%d", &n, &m);
	for (register int i = 1; i <= n; i++)
		G[i].clear(), Gx[i].clear();
	for (register int u, v, i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v);
		G[u].push_back(v);
	}
	
	for (register int i = 1; i <= n; i++)
		if (!dfn[i]) Tarjan(i);
	
	if (scc == 1) return void(puts("0"));
	
	for (register int i = 1; i <= n; i++)
		for (auto j : G[i])
			if (belong[i] != belong[j])
				Gx[belong[i]].push_back(belong[j]);
	
	for (register int i = 1; i <= scc; i++) {
		out[i] = Gx[i].size();
		for (auto j : Gx[i]) in[j]++;	
	}
	int cnt1 = 0, cnt2 = 0;
	for (register int i = 1; i <= scc; i++)
		cnt1 += (in[i] == 0), cnt2 += (out[i] == 0);
	
	printf("%d\n", max(cnt1, cnt2));
}

signed main() {
	int T;
	scanf("%d", &T);
	while (T--) solve();
	return 0;
}

「UVA 12167」 Proving Equivalences

标签:一个   size   ons   max   现在   break   string   bre   using   

原文地址:https://www.cnblogs.com/-Wallace-/p/12829527.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!