洛谷 P2403 所駝門王的寶藏

maniubi發表於2024-09-03

洛谷 P2403 所駝門王的寶藏

題意

有一個 \(R\times C\) 的網格圖,有 \(N\) 個點有傳送門和寶藏。

有三種傳送門:

一種可以傳送至同一行的任意點,

一種可以傳送到同一列的任一點,

一種可以傳送的周圍八個點。

可從任意點開始,任意點結束,求最多走過多少個寶藏。

思路

把網格圖建成圖,縮點後跑拓撲排序求出最長路即可。

但有個問題,若一行上全部都是種類 \(1\) 或一列上全部都是種類 \(2\),最多可建出 \(O(n^2)\) 條邊。

需要最佳化建圖。我們可以對每一行和每一列建一個虛點連向該行,該列所有的點。

若該傳送門是種類 \(1\) 或種類 \(2\),將其連向該行或該列的虛點。

這樣等價於連向每一個點,但減少了邊數。

只需 \(O(n)\) 條邊即可建出等價的圖。

動態開點,要使用時再建立,不使用就不建,可以節省空間。

注意虛點不能算入最後的答案,沒有傳送門的點也不能。

程式碼

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int tot, ver[N << 1], nxt[N << 1], head[N];
int n, r, c, point, in[N], out[N];
int sc, scc[N], stk[N], top, val[N];
int low[N], dfn[N], siz[N], cnt; 
bool instk[N];
int xz[] = {1, 0, -1, 0, 1, 1, -1, -1};
int yz[] = {0, 1, 0, -1, 1, -1, 1, -1};
map <pair<int, int>, int> GID;
map <int, int> HID;
map <int, int> LID;
vector <int> E[N]; 
queue <int> Q;
int dp[N], ans;
void add(int x, int y) {
	ver[++ tot] = y;
	nxt[tot] = head[x];
	head[x] = tot;
}
int H(int x) { // 動態開點
	if (HID.find(x) != HID.end()) return HID[x];
	HID[x] = ++ point; 
	return point; 
}
int L(int x) {
	if (LID.find(x) != LID.end()) return LID[x];
	LID[x] = ++ point; 
	return point; 
}
int id(int x, int y) {
	if (GID.find({x, y}) != GID.end()) 
		return GID[{x, y}];
	GID[{x, y}] = ++ point;
	int id = point;
	add(H(x), id); // 虛點連向每個點
	add(L(y), id);
	return id;
}
void tarjan(int x) {
	dfn[x] = low[x] = ++ cnt;
	stk[++ top] = x;
	instk[x] = 1;
	for (int i = head[x]; i; i = nxt[i]) {
		int y = ver[i];
		if (!dfn[y]) {
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}else if (instk[y]) 
			low[x] = min(low[x], dfn[y]);
	}
	if (dfn[x] == low[x]) {
		sc ++;
		while (stk[top] != x) {
			instk[stk[top]] = 0;
			scc[stk[top]] = sc; 
			siz[sc] += val[stk[top]]; // 加上寶藏個數,不是1
			top --;
		}
		instk[stk[top]] = 0;
		scc[stk[top]] = sc; 
		siz[sc] += val[stk[top]];
		top --;
	}
}
void init() {
	cin >> n >> r >> c;
	for (int i = 1, x, y, t; i <= n; i ++) {
		cin >> x >> y >> t;
		int ID = id(x, y);
		val[ID] = 1; // 有寶藏
		if (t == 1) {
			add(ID, H(x)); // 連向虛點
		} else if (t == 2) {
			add(ID, L(y));
		} else if (t == 3) {
			for (int j = 0; j < 8; j ++) {
				int xx = x + xz[j], yy = y + yz[j];
				if (xx < 1 || xx > r || yy < 1 || yy > c) continue;
				add(ID, id(xx, yy));
			}
		}
	} 
}
void tarjan() { // 縮點
	for (int i = 1; i <= point; i ++) 
		if (!dfn[i]) tarjan(i);
	for (int i = 1; i <= point; i ++) 
		for (int j = head[i]; j; j = nxt[j]) 
			if (scc[i] != scc[ver[j]]) {
				E[scc[i]].push_back(scc[ver[j]]);
				in[scc[ver[j]]] ++;
				out[scc[i]] ++;
			}
}
void tuopu() { // 拓撲
	for (int i = 1; i <= sc; i ++)
		if (!in[i]) {
			Q.push(i);	
			dp[i] = siz[i];
		}
	while (!Q.empty()) {
		int x = Q.front(); Q.pop();
		for (auto y : E[x]) {
			dp[y] = max(dp[y], dp[x] + siz[y]);
			if (!(-- in[y])) Q.push(y);
		}
	}
	for (int i = 1; i <= sc; i ++)
		ans = max(ans, dp[i]);
}
int main() {
	init(); tarjan(); tuopu();
	cout << ans << "\n";
	return 0;
}