洛谷 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;
}