[学习笔记] 强连通分量
Find Problems:
-
https://www.luogu.com.cn/problem/list?type=CF&keyword=强连通分量&page=1
-
https://www.luogu.com.cn/problem/list?type=AT&keyword=强连通分量&page=1
一、DFS Forest
从这张经典图说起:
在给定的有向图 \(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\) 的安全的花费,那么最小花费为:
肯定,每个连通块要取最小的,那么根据乘法原理,肯定是每个 SCC 中 \(w\) 为 \(\min w\) 的数量,即:
#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。
那么这个导出子图就应该是一条链,如果有分叉就是这个形状:
这样就会导致 \(2, 3\) 相互不可达。
所以答案就分别是:最长链长度,最长链数量。
然后就记 \(f_u\) 为终点为 \(u\) 的最长链长度,\(g_u\) 是终点为 \(u\) 的最长链数量。
那么从 \(u\) 转移到 \(v\) 的转移方程就是:
然后就是个 \(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
缩点之后 \(1, 2, 4\) 被缩成一个点,为 \(1\):
所点之后每个点的权值(钱数)就是点所在 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
样例图:
首先我们考虑建图。
将每一行有 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