P8059 [POI2003] Monkeys
这道题思路比较巧妙也比较常见:正难则反。
考虑正推,仿佛要带撤销并查集,反正很难,
面对这种删边问题,一般都是通过倒推的方法变为加边问题,往往会好做。
所以考虑倒推,问题转换为:通过不断加边,则每个点的掉落时间为它第一次与
但是,一个问题来了:在一块连通块
可用链表维护,记录一个
而且每个并查集维护三个信息:
-
,记录 节点的父亲,初始值: ; -
记录 节点的连通块的节点中,编号最小的点; -
记录 节点的连通块的节点中,编号最大的点;
其中
若要与
而且每次合并时都是由大合并到小的,要保证
#include <cstdio>
#include <iostream>
using namespace std;
const int N = 4e5 + 10;
int a[N][3];
int ans[N], nxt[N];
int p[N], w[N], vis[N][3];
int fa[N], head[N], tail[N];
int find(int x){
if (fa[x] == x)return x;
return fa[x] = find(fa[x]);
}
void merge (int u, int v, int p) {
int fu = find(u), fv = find(v);
if (fu == fv) return ;
if (fu > fv) swap (fu, fv);
if (fu == 1 && p != -1) { // 更新答案。其中 p = -1 代表是没有删过的边将它加上,所以不更新答案。
for (int use = head[fv]; use; use = nxt[use]) ans[use] = p;
}
fa[fv] = fu; nxt[tail[fu]] = head[fv]; tail[fu] = tail[fv];
}
int main() {
int n, m; scanf ("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf ("%d%d", &a[i][1], &a[i][2]); ans[i] = -1;
}
for (int i = 0; i < m; ++i) {
scanf ("%d%d", &p[i], &w[i]);
vis[p[i]][w[i]] = 1; // 记录此条边会被删掉。
}
for (int i = 1; i <= n; ++i) fa[i] = head[i] = tail[i] = i, nxt[i] = 0;
for (int i = 1; i <= n; ++i) { // 若从来没有删过的边,一开始就要加上。
if (!vis[i][1] && a[i][1] != -1) merge (i, a[i][1], -1);
if (!vis[i][2] && a[i][2] != -1) merge (i, a[i][2], -1);
}
for (int i = m - 1; i >= 0; --i) {
int u = p[i], v = a[p[i]][w[i]];
merge (u, v, i);
}
for (int i = 1; i <= n; ++i) printf("%d\n",ans[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具