洛谷 P2403 所驼门王的宝藏

洛谷 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;
}
posted @ 2024-09-03 07:59  maniubi  阅读(6)  评论(0编辑  收藏  举报