消消乐

消消乐

题意

在一个网格图中,有两种功能方块,一种可以引爆同一行的功能方块,一种可以引爆同一列的。

求最少引爆多少个功能方块能消除所有功能方块。

思路

先想到建图,每个横向的功能方块向同一行的功能方块连有向边,每个竖向的功能方块向同一列的功能方块脸有向边,最后缩点求出入度为 \(0\) 的点的个数即可。

但这样连边变得数量最多是 \(O(n^2)\) 的。我们可以每行每列建一个中转点,它连向该行/列的每个点,功能方块直接和他连就行了,边的数量是 \(O(n)\) 的。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 5;

int n, m, r, ans;
int t[N], x[N], y[N], sc, scc[N], in[N];
int low[N], dfn[N], cnt, stk[N], top;
bool instk[N], u[N];
vector <int> E[N];


void tarjan(int x) {
	low[x] = dfn[x] = ++ cnt;
	stk[++ top] = x; instk[x] = 1;
	for (auto y : E[x]) {
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		} else if (instk[y])
			low[x] = min(low[x], dfn[y]);
	}
	if (low[x] == dfn[x]) {
		sc ++;
		while (top && stk[top] != x) {
			scc[stk[top]] = sc;
			instk[stk[top]] = 0;
			if (stk[top] <= r) u[sc] = 1;
			top --;
		}
		scc[stk[top]] = sc;
		if (stk[top] <= r) u[sc] = 1;
		instk[stk[top]] = 0;
		top --;
	}
}

int main() {
	freopen("lele.in", "r", stdin);
	freopen("lele.out", "w", stdout);
	
	cin >> n >> m >> r;
	for (int i = 1; i <= r; i ++) {
		cin >> t[i] >> x[i] >> y[i];
		E[r + x[i]].push_back(i);
		E[r + n + y[i]].push_back(i); 
		if (t[i] == 1) E[i].push_back(r + x[i]);
		else E[i].push_back(r + n + y[i]);
	} 
	
	for (int i = 1; i <= r + n + m; i ++)
		if (!dfn[i]) tarjan(i);
	
	for (int i = 1; i <= r + n + m; i ++) {
		for (auto y : E[i]) {
			if (scc[y] != scc[i] && u[scc[i]] && u[scc[y]]) {
				in[scc[y]] ++;
			}
		}
	}
	
	for (int i = 1; i <= sc; i ++) 
		if (in[i] == 0 && u[i]) ans ++;
	
	cout << ans << "\n";
	return 0;
}
posted @ 2024-10-16 21:52  maniubi  阅读(9)  评论(0编辑  收藏  举报