洛谷 P2403 所驼门王的宝藏 —— tarjan + 最长路

题目链接:点我啊╭(╯^╰)╮

题目大意:

     r ∗ c r * c rc 的图,有 n n n 个宝藏点,宝藏点有传送门
    传送门分三种:横向任意飞、纵向任意飞、九宫格内任意飞
    初始点任意,求最多得到几个宝藏???

解题思路:

    一眼望去缩点后 d p dp dp 。。。(也许真的是一眼吧)
    关键难点在于建图,如果有 n n n 个横向的门,那么边数就成 n 2 n^2 n2
    发现一个连通分量的点直接缩掉,也就是在同一行的横向门当成环处理即可
    对于在同一行的非横向门,建一条环指向这个点的边即可
    所以排序处理一下即可,细节颇多,纵向门也同样处理
    九宫格门直接暴力建即可
    跑完 t a r j a n tarjan tarjan 之后求个最长路即可

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
typedef pair <int,int> pii;
const int maxn = 1e5 + 5;
int dx[10] = {1, 1, 1, 0, 0, -1, -1, -1};
int dy[10] = {1, 0, -1, 1, -1, 1, 0, -1};
map <pii, int> mp;
vector <int> g[maxn], g2[maxn];
struct node{
	int x, y, op, id;
} p[maxn];
int n, r, c, ans, tot, top, numc;
int dfn[maxn], low[maxn], vis[maxn], st[maxn];
int dp[maxn], sum[maxn], col[maxn];

inline void add(int u, int v){
	g[u].push_back(v);
}
bool xcmp(const node &A, const node &B){
	if(A.x ^ B.x) return A.x < B.x;
	if(A.op == 1) return true;
	if(B.op == 1) return false;
	return A.y < B.y;
}
bool ycmp(const node &A, const node &B){
	if(A.y ^ B.y) return A.y < B.y;
	if(A.op == 2) return true;
	if(B.op == 2) return false;
	return A.x < B.x;
}

void tarjan(int u){
	dfn[u] = low[u] = ++tot;
	st[++top] = u;
	vis[u] = 1;
	for(auto v : g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u], low[v]);
		} else if(vis[v]) low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]){
		++numc;
		while(st[top+1] ^ u){
			col[st[top]] = numc;
			sum[numc]++;
			vis[st[top--]] = 0;
		}
	}
}

void dfs(int u, int fa){
	if(dp[u] > sum[u]) return;
	dp[u] = sum[u];
	for(auto v : g2[u]){
		if(v == fa) continue;
		dfs(v, u);
		dp[u] = max(dp[u], dp[v] + sum[u]);
	}
}

signed main() {
	scanf("%d%d%d", &n, &r, &c);
	for(int i=1; i<=n; i++){
		scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].op);
		p[i].id = mp[{p[i].x, p[i].y}] = i;
	}
	sort(p+1, p+1+n, xcmp);
	for(int i=1, fi=1, la=1; i<=n; i++){
		if(p[i].x ^ p[i+1].x){
			if(fi ^ la) add(p[la].id, p[fi].id);
			fi = la = i + 1;
		} else{
			if(p[la].op == 1) add(p[la].id, p[i+1].id);
			if(p[i+1].op == 1) la = i + 1;
			if(p[fi].op ^ 1) fi = la = i + 1;
		}
	}
	sort(p+1, p+1+n, ycmp);
	for(int i=1, fi=1, la=1; i<=n; i++){
		if(p[i].y ^ p[i+1].y){
			if(fi ^ la) add(p[la].id, p[fi].id);
			fi = la = i + 1;
		} else{
			if(p[la].op == 2) add(p[la].id, p[i+1].id);
			if(p[i+1].op == 2) la = i + 1;
			if(p[fi].op ^ 2) fi = la = i + 1;
		}
	}
	for(int i=1; i<=n; i++){
		if(p[i].op ^ 3) continue;
		for(int j=0; j<8; j++)
			if(mp.count({p[i].x+dx[j], p[i].y+dy[j]}))
				add(p[i].id, mp[{p[i].x+dx[j], p[i].y+dy[j]}]);
	}
	for(int i=1; i<=n; i++)
		if(!dfn[i]) tarjan(i);
		
	for(int i=1; i<=n; i++) {
		for(auto v : g[i])
			if(col[i] ^ col[v])
				g2[col[i]].push_back(col[v]);
	}
	for(int i=1; i<=numc; i++){
		if(dp[i] <= sum[i]) dfs(i, 0);
		ans = max(ans, dp[i]);
	}
	printf("%d\n", ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值