所驼门王的宝藏 - 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();
	}

}
posted @ 2022-07-17 00:02  Yaqu  阅读(46)  评论(1)    收藏  举报