所驼门王的宝藏 - scc缩点 + 虚点思想
所驼门王的宝藏
https://www.luogu.com.cn/problem/P2403
题意
有k个宝藏点 n * m的格子 每个宝藏点都有一个种类的传送门(1<=k<=1e5 1<=n,m<=1e6)
类型 1 代表可以去往同一行的任意一格
类型 2 代表可以去往同一列的任意一格
类型 3 代表可以去往格子四周相邻八个点的任意一个
问最多能去到多少个宝藏点
思路
因为数据较大 光建图就要n*n的复杂度 所以要优化建图
可以设置行虚点和列虚点来优化 虚点的权值为0 只为了方便建图
如果一个点是1类型的他给予要和一行的所有点都连一条边 而如果一行有多个类型为1的点就拿极端举例:一行有1e5个类型为1的点 那么就要建 1e5 * (1e5 - 1) / 2条边 复杂度太高了 而如果我们用一个行虚点 首先指向这行上的每个点 如果这行上有一个类型为1的点只要让这个类型为1的点和这个行虚点建一条边就可以达到同样的效果 建边复杂度就变成O(n)了
建完图 跑一下tarjan 然后进行缩点 同时记录缩点的权值 即非虚点的个数
然后在缩完点的图跑一遍dfs找权值最大的一条路径即可
#include<bits/stdc++.h>
#include<iostream>
#include<vector>
#include<unordered_map>
#define ll int
#define ull unsigned long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const ll inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-10;
const ll N = 1e6 + 5;
const int M = 1e6 + 5;
const ll mod = 1e9 + 7;
//idx代表行虚点 idy代表列虚点的标号 w[]代表是否是虚点 值为0就是虚点
ll idx[M], idy[M], w[M], dfn[N], low[N], cnt, sc;
//ins入度
ll c[N], ins[N];
//in[]判断是否在栈里
ll n, m, k, tot, in[N], sz[N], val[N];
vector<ll>g[N], scc[N], edge[N];
//mp[{x, y}]标记 坐标为x y的点的序号
map<pair<ll, ll>, ll>mp;
stack<ll>st;
struct node {
ll x, y, p;
}a[N];
void dfs(ll x) {
sz[x] = val[x];
for (ll to : edge[x]) {
//记忆化 sz已经有值就不用再去向下遍历了
if (sz[to]) {
sz[x] = max(sz[x], sz[to] + val[x]);
continue;
}
dfs(to);
sz[x] = max(sz[x], sz[to] + val[x]);
}
}
//强连通分量 缩点板子
void tarjan(ll x) {
low[x] = dfn[x] = ++cnt;
st.push(x);
in[x] = 1;
for (auto to : g[x]) {
if (!dfn[to]) {
tarjan(to);
low[x] = min(low[x], low[to]);
}
else {
if (in[to]) low[x] = min(low[x], dfn[to]);
}
}
if (dfn[x] == low[x]) {
++sc;
while (1) {
ll now = st.top();
st.pop();
in[now] = 0;
val[sc] += w[now];
scc[sc].push_back(now);
c[now] = sc;
if (now == x) break;
}
}
}
void solve() {
cnt = 0;
ll bas = 1e6;
cin >> k >> n >> m;
ll x, y, p;
tot = 0;
//一开始给点的编号
for (int i = 1; i <= k; i++) {
cin >> a[i].x >> a[i].y >> a[i].p;
mp[{a[i].x, a[i].y}] = ++tot;
}
for (int i = 1; i <= k; i++) {
x = a[i].x;
y = a[i].y;
p = a[i].p;
//如果行和列虚点还未被标号就标号
if (!idx[x]) idx[x] = ++tot;
if (!idy[y]) idy[y] = ++tot;
//++tot;
//cout << tot << "\n";
//if (!mp.count({x, y})) mp[{x, y}] = ++tot;
ll idnum = mp[{x, y}];
w[idnum] = 1;
g[idx[x]].push_back(idnum);
g[idy[y]].push_back(idnum);
if (p == 1)
g[idnum].push_back(idx[x]);
else if(p == 2)
g[idnum].push_back(idy[y]);
else {
//任意门 遍历周围八个点 如果有点就加边 否则不加边
for (ll j = x - 1; j <= x + 1; j++) {
if (j > n || j < 1) continue;
for (ll k = y - 1; k <= y + 1; k++) {
if (j == x && k == y || k < 1 || k > n) continue;
//判断该位置是否存在点
if (mp.count({ j, k }))
g[idnum].push_back(mp[{j,k}]);
}
}
}
}
for (int i = 1; i <= tot; i++) {
if (!dfn[i]) tarjan(i);
}
//缩点 再建新图
for (int i = 1; i <= tot; i++) {
for (ll to : g[i]) {
if (c[i] == c[to]) continue;
edge[c[i]].push_back(c[to]);
ins[c[to]]++;
}
}
ll ans = 0;
for (int i = 1; i <= sc; i++) {
//入度为0 为根节点
if (!ins[i]) {
dfs(i);
ans = max(ans, sz[i]);
}
}
//cout << val[14] << " " << val[13] << " " << val[9] << ' ' << val[7] << " " << val[1] << " " << val[0] << "\n";
cout << ans << '\n';
}
signed main() {
IOS;
int t = 1;
//cin >> t;
while (t--) {
solve();
}
}