[学习笔记] 强连通分量

Find Problems:

一、DFS Forest

从这张经典图说起:

image

在给定的有向图 \(G = (V, E)\) 内,遍历这张图,其中边分为 \(4\) 种:

  • 树边(tree edge):黑色的边,每一次从 \(u\) 访问到一个未访问的节点 \(v\),则称 \((u, v)\)树边

  • 返祖边(back edge):红色的边,每一次从 \(u\) 回溯到一个 \(u\) 的已经访问的祖先 \(v\),则称 \((u, v)\)返祖边

  • 横叉边(cross edge):蓝色的边,每一次 \(u\) 访问到一个已经访问过的节点 \(v\),但 \(v\) 不是 \(u\) 的祖先,则称 \((u, v)\)横叉边

  • 前向边(forward edge):绿色的边,每一次 \(u\) 访问到一个还没有访问过的节点 \(v\),并且 \(u\)\(v\) 的祖先,则称 \((u, v)\)前向边

其中第一个被访问的节点 \(rt\) 被称为 DFS Forest 的

二、强连通分量

  • 可达 是指 \(u, v\) 之间至少有一条路径。

  • 弱连通(weakly connected) 是指 \(u, v\) 两点,\(u, v\) 可达或 \(v, u\) 可达至少满足一个。

  • 弱连通图(weakly connected graph) 是指将一张有向图的有向边换成无向边,可以构成一个连通块的有向图。

  • 强连通(strongly connected) 是指 \(u, v\) 两点,\(u, v\) 可达并且 \(v, u\) 可达。

  • 强连通图(strongly connected graph) 是指有向图中任意两点 \(u, v(u \neq v)\) 都是强连通的。

  • 强连通分量(strongly connected component) 是指有向图中极大强连通图。

性质:若 \(u, v\) 强连通,且 \(v, w\) 强连通,则 \(u, w\) 强连通(具有传递性)。

三、Tarjan

  • 性质 1:强连通分量的分布一定是有向图切掉一些边,形成一堆连通块,这些连通块就是强连通分量。

  • 性质 2:DFS Forest 上如果一个连通分量没有边连出,那么这个连通分量就是一个 强连通分量

  • \(stk\):一个栈,还没有被切掉的节点。

  • \(dfn_u\):节点 \(u\) 的 DFS 序。

  • \(low_u\):在 \(u\) 的子树内能回溯到的最早的在栈中的节点。

然后从 \(u\)\(v\) dfs 有三种情况:

  • \(v\) 未被访问:继续 dfs,low[u] = min (low[u], low[v])

  • \(v\) 已被访问,并且在 \(stk\) 中,low[u] = min (low[u], dfn[v])

  • \(v\) 已被访问,不在 \(stk\) 中,就当无事发生。

如果 \(dfn_u = low_u\),容易发现 \(u\) 已经寄了,所以栈中 \(u\) 以上的(含 \(u\))的所有节点都构成一个强连通分量。

求 SCC 的板子(洛谷 B3609)代码:

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
	Z x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
vector < int > G[N];
int n, m, dfn[N], low[N], isin[N], time_click, scc[N], cnt;
vector < int > sccs[N];
stack < int > stk;
inline void tarjan (int u) {
	low[u] = dfn[u] = ++ time_click;
	isin[u] = 1;
	stk.push (u);
	for (int v : G[u]) {
		if (!dfn[v]) {tarjan (v), low[u] = min (low[u], low[v]);}
		else {if (isin[v]) low[u] = min (low[u], dfn[v]);}
	}
	if (dfn[u] == low[u]) {
		cnt ++;
		while (true) {
			int v = stk.top (); scc[v] = cnt, isin[v] = 0, stk.pop ();
			if (u == v) break;
		}
	}
}
bool Memory_Ends;
signed main () {
	fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
	read (n), read (m);
	for (int i = 1;i <= m; ++ i) {
		int u, v;
		read (u), read (v);
		G[u].push_back (v);
	} 
	for (int i = 1;i <= n; ++ i) {if (!dfn[i]) tarjan (i);}
	for (int i = 1;i <= n; ++ i) sccs[scc[i]].push_back (i);
	set < int > st; st.clear ();
	printf ("%d\n", cnt);
	for (int i = 1;i <= n; ++ i) {
		if (st.count (i)) continue;
		for (int u : sccs[scc[i]]) printf ("%d ", u), st.insert (u);
		printf ("\n");
	}
	fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
	return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

四、Codeforces 427C - Checkposts

http://codeforces.com/problemset/problem/427/C

首先我们求出强连通分量,设有 \(k\) 个,分别为 \(\{u_{1, 1}, u_{1, 2}, \dots, u_{1, |u_1|}\}\)\(\{u_{2, 1}, u_{2, 2}, \dots, u_{2, |u_2|}\}\),...,\(\{u_{k, 1}, u_{k, 2}, \dots, u_{k, |u_k|}\}\)

贪心,可得每一个强连通分量内,建造的花费为 SCC 内部的点的最小花费,i. e.

\(w_i\) 为保证 \(i\) 的安全的花费,那么最小花费为:

\[\sum_{i = 1}^{k} \min_{j = 1}^{|u_i|}w_{u_{i,j}} \]

肯定,每个连通块要取最小的,那么根据乘法原理,肯定是每个 SCC 中 \(w\)\(\min w\) 的数量,即:

\[\prod_{i = 1}^{k} \sum_{j = 1}^{|u_i|} \left[w_{u_{i,j}} = \min_{x=1}^{|u_i|}w_{u_{i,x}}\right] \]

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
	Z x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
vector < int > G[N];
int n, m, dfn[N], low[N], isin[N], time_click, scc[N], cnt;
vector < int > st[N];
stack < int > stk;
inline void tarjan (int u) {
	low[u] = dfn[u] = ++ time_click;
	isin[u] = 1;
	stk.push (u);
	for (int v : G[u]) {
		if (!dfn[v]) {tarjan (v), low[u] = min (low[u], low[v]);}
		else {if (isin[v]) low[u] = min (low[u], dfn[v]);}
	}
	if (dfn[u] == low[u]) {
		cnt ++;
		while (true) {
			int v = stk.top (); scc[v] = cnt, isin[v] = 0, stk.pop ();
			if (u == v) break;
		}
	}
}
ll w[N], val[N];
const ll mod = 1e9 + 7;
bool Memory_Ends;
signed main () {
	fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
	read (n);
	for (int i = 1;i <= n; ++ i) read (w[i]);
	read (m);
	for (int i = 1;i <= m; ++ i) {
		int u, v;
		read (u), read (v);
		G[u].push_back (v);
	} 
	for (int i = 1;i <= n; ++ i) {if (!dfn[i]) tarjan (i);}
	for (int i = 1;i <= cnt; ++ i) val[i] = 2e9;
	for (int i = 1;i <= n; ++ i) val[scc[i]] = min (val[scc[i]], w[i]);
	ll ans1 = 0, ans2 = 1;
	for (int i = 1;i <= cnt; ++ i) ans1 += val[i];
	for (int i = 1;i <= n; ++ i) st[scc[i]].push_back (i);
	for (int i = 1;i <= cnt; ++ i) {
		ll jd = 0;
		for (int u : st[i]) {
			if (w[u] == val[i]) jd ++;
		}
		ans2 = ans2 * jd % mod;
	}
	printf ("%lld %lld\n", ans1, ans2);
	fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
	return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

五、洛谷 P2272 [ZJOI2007] 最大半连通子图

https://www.luogu.com.cn/problem/P2272

首先先考虑一个弱化版本:原图是 DAG。

那么这个导出子图就应该是一条链,如果有分叉就是这个形状:

image

这样就会导致 \(2, 3\) 相互不可达。

所以答案就分别是:最长链长度,最长链数量。

然后就记 \(f_u\) 为终点为 \(u\) 的最长链长度,\(g_u\) 是终点为 \(u\) 的最长链数量。

那么从 \(u\) 转移到 \(v\) 的转移方程就是:

\[f_v = \max(f_v, f_u + 1) \]

\[g_v = \begin{cases}g_v, f_v > f_u + 1\\g_u + g_v, f_v = f_u + 1\\ g_u, f_v < f_u + 1\end{cases} \]

然后就是个 \(f\) 的最大值计数(权值为 \(g\) 的值)。

回到原问题:很简单,直接缩点,就是 DAG,只不过每个点的贡献就不是 \(1\) 了,而是缩点后每个点代表的原来的点的数量。

缩点后一定要判重边 & 自环!!1

缩点后一定要判重边 & 自环!!1

缩点后一定要判重边 & 自环!!1

缩点后一定要判重边 & 自环!!1

缩点后一定要判重边 & 自环!!1

还有就是所有 \(v\) 的入边都转移完了再去转移 \(v\) 的出边。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
	Z x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
vector < int > G[N];
int n, m, dfn[N], low[N], isin[N], time_click, scc[N], cnt, siz[N];
vector < int > sccs[N];
stack < int > stk;
inline void tarjan (int u) {
	low[u] = dfn[u] = ++ time_click;
	isin[u] = 1;
	stk.push (u);
	for (int v : G[u]) {
		if (!dfn[v]) {tarjan (v), low[u] = min (low[u], low[v]);}
		else {if (isin[v]) low[u] = min (low[u], dfn[v]);}
	}
	if (dfn[u] == low[u]) {
		cnt ++;
		while (true) {
			int v = stk.top (); scc[v] = cnt, isin[v] = 0, stk.pop ();
			if (u == v) break;
		}
	}
}
int mod;
int f[N], g[N], st[N], vis[N], deg[N];
vector < pair < int, int > > ed;
set < int > gr[N];
bool Memory_Ends;
signed main () {
	fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
	read (n), read (m), read (mod);
	for (int i = 1;i <= m; ++ i) {
		int u, v;
		read (u), read (v);
		G[u].push_back (v);
		ed.push_back (make_pair (u, v));
	}
	for (int i = 1;i <= n; ++ i) {
		if (!scc[i]) tarjan (i);
	}
	for (int i = 1;i <= n; ++ i) {
		sccs[scc[i]].push_back (i);
		siz[scc[i]] ++;
	}
	for (int i = 1;i <= cnt; ++ i) st[i] = 1;
	for (pair < int, int > e : ed) {
		int u = e.first, v = e.second;
		u = scc[u], v = scc[v];
		if (u != v) gr[u].insert (v)/*, printf ("%d %d\n", u, v)*/;
		if (u != v) st[v] = 0;
	}
	for (int i = 1;i <= n; ++ i) {
		for (int x : gr[i]) deg[x] ++;
	}
//	printf ("ok: ");
//	for (int i = 1;i <= cnt; ++ i) {
//		if (st[i]) printf ("%d ", i);
//	}
//	printf ("\n");
	queue < int > q; 
	while (!q.empty ()) q.pop (); 
	for (int i = 1;i <= cnt; ++ i) {
		if (!deg[i]) f[i] = siz[i], g[i] = 1, q.push (i);
	}
	while (!q.empty ()) {
		int u = q.front (); q.pop ();
		if (vis[u]) continue;
		vis[u] = 1;
		for (int v : gr[u]) {
//			printf ("f[%d] -> f[%d], suc = %d\n", u, v, f[v] <= f[u] + siz[v]);
			if (f[v] > f[u] + siz[v]) ;
			else if (f[v] == f[u] + siz[v]) g[v] = (g[v] + g[u]) % mod;
			else f[v] = f[u] + siz[v], g[v] = g[u];
			deg[v] --;
			if (!deg[v]) q.push (v);
		}
	}
//	for (int i = 1;i <= n; ++ i) {
//		printf ("%d ", scc[i]);
//	}
//	printf ("\n");
//	for (int i = 1;i <= cnt; ++ i) {
//		printf ("qwq: %d %d\n", f[i], g[i]);
//	}
	int mxlen = -1, tot = 0;
	for (int i = 1;i <= cnt; ++ i) {
		if (f[i] > mxlen && g[i] > 0) mxlen = f[i], tot = g[i];
		else if (f[i] == mxlen) tot = (tot + g[i]) % mod;
	}
	printf ("%d\n%d\n", mxlen, tot);
	fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
	return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

六、洛谷 P3627 [APIO2009] 抢掠计划

https://www.luogu.com.cn/problem/P3627

首先考虑缩点,然后就可以建出一张新图,拿样例举个例子:

1 2 
2 3 
3 5 
2 4 
4 1 
2 6 
6 5

image

缩点之后 \(1, 2, 4\) 被缩成一个点,为 \(1\)

image

所点之后每个点的权值(钱数)就是点所在 SCC 的钱数的总和。

然后把起点所能到的子图留下来,容易发现是一张 DAG,然后跑 dp 就行了,注意终点一定要在酒吧所在的 SCC。

注意重边 & 自环。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
	Z x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	tmp = !f ? x : -x;
}
const int N = 5e5 + 5;
vector < int > G[N];
int n, m, dfn[N], low[N], isin[N], time_click, scc[N], cnt;
stack < int > stk;
inline void tarjan (int u) {
	low[u] = dfn[u] = ++ time_click;
	isin[u] = 1;
	stk.push (u);
	for (int v : G[u]) {
		if (!dfn[v]) {tarjan (v), low[u] = min (low[u], low[v]);}
		else {if (isin[v]) low[u] = min (low[u], dfn[v]);}
	}
	if (dfn[u] == low[u]) {
		cnt ++;
		while (true) {
			int v = stk.top (); scc[v] = cnt, isin[v] = 0, stk.pop ();
			if (u == v) break;
		}
	}
}
ll val[N], w[N], f[N];
int deg[N];
bool used[N];
vector < int > bar;
vector < int > G2[N], F[N];
int sta;
bool Memory_Ends;
signed main () {
	fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
	read (n), read (m);
	vector < PII > ed; ed.clear ();
	for (int i = 1;i <= m; ++ i) {
		int u, v;
		read (u), read (v);
		G[u].push_back (v);
		ed.push_back (make_pair (u, v)); 
	} 
	for (int i = 1;i <= n; ++ i) {if (!dfn[i]) tarjan (i);}
//	for (int i = 1;i <= n; ++ i) printf ("%d ", scc[i]); printf ("\n"); 
	for (int i = 1;i <= n; ++ i) read (w[i]);
	read (sta);
	int _; read (_);
	for (int i = 1;i <= _; ++ i) {
		int x; read (x);
		bar.push_back (x);
	}
	for (int i = 1;i <= n; ++ i) val[scc[i]] += 1ll * w[i];
	set < PII > st; st.clear ();
	for (PII e : ed) {
		int u = e.first, v = e.second;
		u = scc[u], v = scc[v];
		if (u ^ v) st.insert (make_pair (u, v)); 
	}
	for (PII e : st) {
//		printf ("%d %d\n", e.first, e.second);
		G2[e.first].push_back (e.second);
	}
	sta = scc[sta];
	queue < int > q;
	q.push (sta);
	for (int i = 1;i <= cnt; ++ i) deg[i] = 0;
	while (!q.empty ()) {
		int u = q.front (); q.pop ();
		if (used[u]) continue;
		used[u] = true;
		for (int v : G2[u]) {
			F[u].push_back (v);
//			printf ("%d %d\n", u, v);
			deg[v] ++;
			q.push (v);
		}
	}
	q.push (sta);
	for (int i = 1;i <= cnt; ++ i) f[i] = val[i];
	while (!q.empty ()) {
		int u = q.front (); q.pop ();
		if (deg[u]) continue;
		for (int v : F[u]) {
			deg[v] --;
			f[v] = max (f[v], f[u] + val[v]);
			if (!deg[v]) {
				q.push (v);
			}
		}
	}
	ll ans = 0;
	for (int i = 1;i <= bar.size (); ++ i) ans = max (ans, f[scc[bar[i - 1]]]);
	printf ("%lld\n", ans);
	fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
	return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

七、洛谷 P2403 [SDOI2010] 所驼门王的宝藏

https://www.luogu.com.cn/problem/P2403

样例图:

image

首先我们考虑建图。

将每一行有 1 类型门的节点建成一个环,然后将环上任意一点连向每一行对应剩下的节点。

每一列同理。

然后 3 号门就暴力连边即可。

建图之后跑一遍 Tarjan,然后带权求最长链即可。

我的代码不开 O2 \(80\) 分,开了 O2 才 \(100\) 分,所以仅供参考。

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
#ifdef LOCAL
#include "algo/debug.h"
#else
#define debug(...) 42
#endif
typedef long long ll;
typedef pair < int, int > PII;
typedef int itn;
mt19937 RND_MAKER (chrono :: steady_clock :: now ().time_since_epoch ().count ());
inline ll randomly (const ll l, const ll r) {return (RND_MAKER () ^ (1ull << 63)) % (r - l + 1) + l;}
bool Memory_Begins;
const double pi = acos (-1);
//__gnu_pbds :: tree < Key, Mapped, Cmp_Fn = std :: less < Key >, Tag = rb_tree_tag, Node_Upadte = null_tree_node_update, Allocator = std :: allocator < char > > ;
//__gnu_pbds :: tree < PPS, __gnu_pbds :: null_type, less < PPS >, __gnu_pbds :: rb_tree_tag, __gnu_pbds :: tree_order_statistics_node_update > tr;
template < class Z >
inline void read (Z &tmp) {
	Z x = 0, f = 0;
	char c = getchar ();
	for ( ; c < '0' || c > '9' ; c = getchar ()) f |= (c == '-');
	for ( ; c >= '0' && c <= '9' ; c = getchar ()) x = (x << 1) + (x << 3) + (c & 15);
	tmp = !f ? x : -x;
}
const int N = 1e5 + 5;
vector < int > G[N];
int dfn[N], low[N], isin[N], time_click, scc[N], cnt;
stack < int > stk;
inline void tarjan (int u) {
	low[u] = dfn[u] = ++ time_click;
	isin[u] = 1;
	stk.push (u);
	for (int v : G[u]) {
		if (!dfn[v]) {tarjan (v), low[u] = min (low[u], low[v]);}
		else {if (isin[v]) low[u] = min (low[u], dfn[v]);}
	}
	if (dfn[u] == low[u]) {
		cnt ++;
		while (true) {
			int v = stk.top (); scc[v] = cnt, isin[v] = 0, stk.pop ();
			if (u == v) break;
		}
	}
}
int n, r, c;
int room[N][3], val[N], dp[N], deg[N];
vector < int > type[1000005], o[1000005];
vector < int > gr[N];
map < pair < int, int >, int > mp;
vector < pair < int, int > > ed;
bool Memory_Ends;
signed main () {
	fprintf (stderr, "%.3lf MB\n", (&Memory_Begins - &Memory_Ends) / 1048576.0);
	read (n), read (r), read (c);
	mp.clear ();
	for (int i = 1;i <= n; ++ i) {
		read (room[i][0]), read (room[i][1]), read (room[i][2]);
		mp[make_pair (room[i][0], room[i][1])] = i;
	}
	for (int i = 1;i <= n; ++ i) {
		if (room[i][2] == 1) type[room[i][0]].push_back (i);
		if (room[i][2] != 1) o[room[i][0]].push_back (i);
	}
	for (int i = 1;i <= r; ++ i) {
		for (int j = 0;j < type[i].size (); ++ j) {
			if (j + 1 == type[i].size ()) G[type[i][j]].push_back (type[i][0]);
			else G[type[i][j]].push_back (type[i][j + 1]);
		}
		if (type[i].empty ()) continue;
		int st = type[i][0];
		for (int u : o[i]) {
			G[st].push_back (u);
		}
	}
	for (int i = 1;i <= n; ++ i) {
		if (room[i][2] == 1) type[room[i][0]].pop_back ();
		if (room[i][2] != 1) o[room[i][0]].pop_back ();
	}	
	for (int i = 1;i <= n; ++ i) {
		if (room[i][2] == 2) type[room[i][1]].push_back (i);
		if (room[i][2] != 2) o[room[i][1]].push_back (i);
	}
	for (int i = 1;i <= c; ++ i) {
		for (int j = 0;j < type[i].size (); ++ j) {
			if (j + 1 == type[i].size ()) G[type[i][j]].push_back (type[i][0]);
			else G[type[i][j]].push_back (type[i][j + 1]); 
		}
		if (type[i].empty ()) continue;
		int st = type[i][0];
		for (int u : o[i]) {
			G[st].push_back (u);
		}
	}
	for (int i = 1;i <= n; ++ i) {
		if (room[i][2] == 3) {
			for (int j = -1;j <= 1; ++ j) {
				for (int k = -1;k <= 1; ++ k) {
					if (!j && !k) continue;
					int _ = mp[make_pair (room[i][0] + j, room[i][1] + k)];
					G[i].push_back (_);
				}
			}
		}
	}
	for (int i = 1;i <= n; ++ i) {
		if (!scc[i]) tarjan (i);
	}
	for (int i = 1;i <= n; ++ i) val[scc[i]] ++;
//	set < pair < int, int > > ed;
	ed.clear ();
	for (int i = 1;i <= n; ++ i) {
		for (int v : G[i]) {
			int u = i;
			u = scc[u];
			int V = scc[v];
			if (u != V) ed.push_back (make_pair (u, V)); 
		}
	}
	for (pair < int, int > e : ed) {
		gr[e.first].push_back (e.second);
		deg[e.second] ++; 
	}
	queue < int > q;
	while (!q.empty ()) q.pop ();
	for (int i = 1;i <= cnt; ++ i) {
		if (!deg[i]) q.push (i);
		dp[i] = val[i]; 
	}
	while (!q.empty ()) {
		int u = q.front (); q.pop ();
		for (int v : gr[u]) {
			dp[v] = max (dp[v], dp[u] + val[v]);
			deg[v] --;
			if (!deg[v]) q.push (v);
		}
	}
	int ans = 0;
	for (int i = 1;i <= cnt; ++ i) ans = max (ans, dp[i]);
	printf ("%d\n", ans);
	fprintf (stderr, "%.3lf ms\n", 1e3 * clock () / CLOCKS_PER_SEC);
	return 0;
}
/*
Things to check:
1. int ? long long ? unsigned int ? unsigned long long ?
2. array size ? is it enough ?
*/

写在最后:做题时遇到的坑点

  1. 缩点后一定要判重边 & 自环!!1
posted @ 2023-06-23 22:00  CountingGroup  阅读(17)  评论(0编辑  收藏  举报