洛谷:P1653 猴子(图存储、逆向思维 删边 -->加边)

洛谷:Monkey

在这里插入图片描述

非常恶心的一道题,题意上来先卡你,让你觉得给出的是一颗 树的结构(一只猴两只手)

但反转了,猴子的手是有向边,图中存在 重边 或 自环

一只猴的手不是抓着另一只猴子的手(松开两方都分离),而是抓着他身体而已,只有两只猴之间没有边了才会分离

所以对于数据,我们不仅要记录 树的结构,也要记录 图的结构(遍历时才能不遗漏信息)

在图建成后,题目会给出 M 次操作:

每次某只猴松开其中一只手,这等价于在我们的图中删去一条边

还要记录分离落地的猴(一个猴落地的前提就是和 1号猴 失去联系,因为 1号猴 尾巴挂在树上始终不会落地)的时间点

M次在图中删边是比较困难的,判断是否产生分离、和 1号 失去联系,要花费的时间也是很高的

所以我们逆向思考一下:

删边困难,但加边却相对容易

我们可以存储 删完所有特定边 后的图的状态,再逆序把边一条条添加上去观察加边的操作是否能使 某 点或块 与 1号 产生联系

若产生联系,显然 删去 这条边的时候 也能相应 断开 联系

思考一下便可确定这就是猴子落地的时间 (不会判断晚)(此时加边能产生联系,更早加更多条边联系显然存在)

剩下就是如何判断加边会产生联系:

我们可以定义标记过的点都是和 1点 有联系的

对于一开始删完所有特定边的图,跑一遍dfs,标记每一个点(这些点显然是最终不会落地的)

之后逆序枚举加边的同时,判断边的两端点是否是: 一边被标记过、一边没被标记过

若是就 dfs 未被标记过的部分,dfs的时候只遍历 未被标记过 的点,这样所有点都只会被遍历一次,时间复杂度是 O ( n + m ) O(n + m) O(n+m) 的,完全可以接受

代码:

#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
#define fx first
#define fy second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1) 
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;

const int N = 200010, M = 400010, MM = 3000010;
int INF = 0x3f3f3f3f, mod = 100003;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int h[N], e[M << 1], ne[M << 1], idx;
int g[N][2], ans[N];
bool vis[N];
struct edge
{
	int a, x;
}ed[M];

void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u, int s) {
	ans[u] = s;
	vis[u] = true;//所以要特别加一个标记数组
	for (int i = h[u]; ~i; i = ne[i]) {
		int j = e[i];
		if (vis[j])continue;//bug —— if(ans[j])continue
		//此题 ans[j] 范围从0 ~ m-1,会导致dfs死循环
		dfs(j, s);
	}
}

int main() {
	cinios;

	cin >> n >> m;

	for (int i = 1; i <= n; i++) {
		int a, b;
		cin >> a >> b;
		g[i][0] = a, g[i][1] = b;//存储树结构
	}

	for (int i = 0; i < m; i++) {
		int u, h;
		cin >> u >> h;
		h--;

		ed[i] = { u,g[u][h] };//把删去的边记录下来
		g[u][h] = -1;//删边
	}

	mem(h, -1);
	for (int i = 1; i <= n; i++) {
		if (g[i][0] != -1)add(i, g[i][0]), add(g[i][0], i);
		if (g[i][1] != -1)add(i, g[i][1]), add(g[i][1], i);
		//建删去特定边后的图
	}

	dfs(1, -1);//初始标记永远在树上的猴子

	for (int i = m - 1; i >= 0; i--) {
		int u = ed[i].a, v = ed[i].x;
		add(u, v), add(v, u);//每次添加边

		if (!ans[u] && ans[v])dfs(u, i);//染色,标记某群猴子的落地时间
		if (!ans[v] && ans[u])dfs(v, i);
	}

	for (int i = 1; i <= n; i++)
		cout << ans[i] << '\n';

	return 0;
}
/*
6 4
6 4
5 1
4 -1
-1 3
-1 -1
1 -1

1 1
2 2
1 2
6 1
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值