P1040 战争 官方题解
Gioush OJ · P1040 战争
战争
【题意简述】
初始有 个彼此独立的联盟,并给出 对敌对参与者。每次提出合并两个参与者所在联盟的请求;若两个联盟之间存在敌对关系,则拒绝,否则合并。输出每次请求是否被接受。
【Hint】
【提示】
并查集维护当前联盟,同时为每个根维护与它敌对的联盟根集合。合并时使用启发式合并,并同步修改敌对集合中的根。
【数据点 1∼31\sim 31∼3】
可以使用位集合维护每个联盟包含的成员及其敌对对象,或者在每次请求时扫描全部初始敌对关系。它们分别得到 与 的做法。
这些做法已经说明,查询所需的信息并不是联盟的全部成员,而是当前联盟之间是否存在敌对关系。
【数据点 444】
暂时接受全部请求并按时间建立并查集重构树。一对敌对参与者首次连通的时刻,就是它们在重构树上最近公共祖先的权值。特殊性质保证只需跳过唯一可能被拒绝的请求。
【正解】
用并查集维护当前联盟。对于每个并查集根 ,维护集合 ,保存与联盟 存在敌对关系的其他联盟根。
始终维持三个不变量:
- 中只保存当前并查集根;
- 若 ,则 ;
- 当且仅当两个联盟之间至少存在一对初始敌对参与者。
初始时每个人各自组成联盟,并把每条敌对关系同时加入两端集合。
处理请求 时,先令 ,。
- 若 ,两个参与者已经在同一联盟中,直接接受;
- 若 ,两个联盟之间存在敌对关系,拒绝请求且不修改任何结构;
- 否则合并两个联盟。
设把较小联盟 合并到较大联盟 。对每个 ,在 中删除 并加入 ,同时将 加入 。最后清空 ,并令 的并查集父亲为 。这一步把所有原来连接到 的敌对关系改接到 ,所以三个不变量继续成立。
每次只迁移较小联盟的敌对集合。一个元素被迁移后,它所属联盟的大小至少翻倍,因此每个元素至多迁移 次。
【复杂度分析】
使用 set 维护敌对集合时,总时间复杂度为
空间复杂度为 。
【参考代码】
#include <set>#include <cstdio>#include <algorithm>#define MAXN 100010using namespace std; int c,n,m,q,Fa[MAXN],Size[MAXN];set <int> Against[MAXN]; int Find(int x) { return Fa[x]==x?x:Fa[x]=Find(Fa[x]);} void Merge(int x,int y) { if (Size[x]>Size[y]) swap(x,y); for (set<int>::iterator it=Against[x].begin();it!=Against[x].end();it++) { int To=Find(*it); Against[To].erase(x); Against[To].insert(y); Against[y].insert(To); } Against[x].clear(); Fa[x]=y; Size[y]+=Size[x];} int main() {#ifndef ONLINE_JUDGE freopen("war.in","r",stdin); freopen("war.out","w",stdout);#endif scanf("%d",&c); scanf("%d%d%d",&n,&m,&q); for (int i=1;i<=n;i++) Fa[i]=i,Size[i]=1; for (int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); Against[x].insert(y); Against[y].insert(x); } while (q-->0) { int x,y; scanf("%d%d",&x,&y); x=Find(x);y=Find(y); if (x==y) { puts("Yes"); continue; } if (Against[x].find(y)!=Against[x].end()) { puts("No"); continue; } Merge(x,y); puts("Yes"); } return 0;}