洛谷: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
*/