1 条题解
-
0
问题重述
给定一个无向图(可能有自环?但看数据范围 应是无自环简单图)。每天,只能使用编号在 范围内的边。问:只用这些边能否构成一个奇环?
奇环判定:在无向图中,存在奇环 ⇔ 图不是二分图。
核心思路
1. 二分图判定
用扩展域并查集:对每个点 拆成两个点 和 ,分别表示“点 属于集合 0”和“点 属于集合 1”。
对于一条边 :
- 如果 和 在同一集合,则出现矛盾,说明有奇环。
- 否则,将 和 合并。
注意:这里代码中的实现略有不同,但思想一致。
2. 离线处理所有区间
暴力处理每个区间是 ,不可行。
关键观察:固定左端点 ,随着右端点 增大,图越来越稠密,一旦出现奇环,之后所有的 都会满足条件。
即对每个 ,存在最小的 使得区间 内的边构成非二分图。那么对于询问 :
- 如果 ,则答案为 YES(有奇环)。
- 否则答案为 NO。
3. 计算 —— 用可撤销并查集+分治
目标:对每个 计算最小的 使得 内有奇环。规定 。
用整体二分/分治的方法:
定义
solve(l, r, vl, vr):- 计算所有 的 ,已知 。
步骤:
- 取中点 。
- 从 开始向右加边,直到出现奇环或到达 ,得到 。
- 递归左半边 :已知右端点不会超过 。
- 递归右半边 :已知左端点从 开始,右端点从 开始。
用可撤销并查集支持加边和回溯。
4. 复杂度分析
- 每个边在递归树中只会被加入常数次(类似线段树分治)。
- 每次操作 (并查集按秩合并)。
- 总复杂度 ,可过 。
代码详解
// 可撤销并查集 int fah[N*2], sz[N*2]; pair<int,int> stk[N*10]; // 记录合并操作 int top;upd(id):加边 ,返回是否形成奇环。核心分治函数:
solve(l, r, vl, vr) { mid = (l+r)/2; // 步骤1:加入[mid, min(vl,r)-1]的边(这些边对所有mid都适用) for i=mid to min(vl,r)-1: upd(i) // 步骤2:从max(mid,vl)开始向右加边,找到第一个奇环位置 for i=max(mid,vl) to vr: if(upd(i)) { ans[mid]=i; break; } // 步骤3:撤销步骤2中加入的边(除了ans[mid]之前的) for i=max(mid,vl) to ans[mid]-1: undo() // 递归左半边 [l,mid-1] if(mid-1 < min(r,vl)) upd(mid-1) // 左半边需要[mid-1]这条边 solve(l,mid-1, vl, ans[mid]) 撤销临时加的边 // 递归右半边 [mid+1,r] for i=max(vl,r) to ans[mid]-1: upd(i) // 右半边需要这些边 solve(mid+1, r, ans[mid], vr) 撤销临时加的边 }5. 处理询问
预处理后,
ans[l]表示左端点为 时,最小的右端点使得有奇环。对于询问 ,我们看 为左端点时:
- 如果 (注意代码中坐标偏移),则 YES
- 否则 NO
坐标偏移说明:代码中 是为了方便处理,实际询问区间是 对应原边 ,但算法中处理的是 对应 。
样例分析
输入:
6 8 2 1 3 1 5 1 6 2 5 2 6 3 4 3 5 5 6 4 8 4 7边1-8如上。询问:
- [4,8]:看从边4开始,最早何时有奇环
- [4,7]:同理
计算得:
- 对左端点4,最早在边8处有奇环 → 询问[4,8]:8≤8 → YES
- 询问[4,7]:8>7 → NO
输出:
NO YES总结
这道题的精髓在于:
- 将“区间是否存在奇环”转化为“对每个左端点求最小右端点”。
- 用整体二分+可撤销并查集高效计算所有 。
- 利用单调性(右端点增大,更易出现奇环)优化。
时间复杂度 ,空间 ,能够处理 的数据规模。
- 1
信息
- ID
- 5316
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者