P1040 · OFFICIAL SOLUTION

P1040 战争 官方题解

Gioush OJ · P1040 战争

战争

【题意简述】

初始有 nn 个彼此独立的联盟,并给出 mm 对敌对参与者。每次提出合并两个参与者所在联盟的请求;若两个联盟之间存在敌对关系,则拒绝,否则合并。输出每次请求是否被接受。

【Hint】

【提示】

并查集维护当前联盟,同时为每个根维护与它敌对的联盟根集合。合并时使用启发式合并,并同步修改敌对集合中的根。

【数据点 1∼31\sim 31∼3】

可以使用位集合维护每个联盟包含的成员及其敌对对象,或者在每次请求时扫描全部初始敌对关系。它们分别得到 O((n+q)n/w)\mathcal{O}((n+q)n/w)O(qmα(n))\mathcal{O}(qm\alpha(n)) 的做法。

这些做法已经说明,查询所需的信息并不是联盟的全部成员,而是当前联盟之间是否存在敌对关系。

【数据点 444】

暂时接受全部请求并按时间建立并查集重构树。一对敌对参与者首次连通的时刻,就是它们在重构树上最近公共祖先的权值。特殊性质保证只需跳过唯一可能被拒绝的请求。

【正解】

用并查集维护当前联盟。对于每个并查集根 uu,维护集合 Against(u)\operatorname{Against}(u),保存与联盟 uu 存在敌对关系的其他联盟根。

始终维持三个不变量:

  1. Against(u)\operatorname{Against}(u) 中只保存当前并查集根;
  2. vAgainst(u)v\in\operatorname{Against}(u),则 uAgainst(v)u\in\operatorname{Against}(v)
  3. vAgainst(u)v\in\operatorname{Against}(u) 当且仅当两个联盟之间至少存在一对初始敌对参与者。

初始时每个人各自组成联盟,并把每条敌对关系同时加入两端集合。

处理请求 (x,y)(x,y) 时,先令 x=Find(x)x=\operatorname{Find}(x)y=Find(y)y=\operatorname{Find}(y)

  • x=yx=y,两个参与者已经在同一联盟中,直接接受;
  • yAgainst(x)y\in\operatorname{Against}(x),两个联盟之间存在敌对关系,拒绝请求且不修改任何结构;
  • 否则合并两个联盟。

设把较小联盟 xx 合并到较大联盟 yy。对每个 vAgainst(x)v\in\operatorname{Against}(x),在 Against(v)\operatorname{Against}(v) 中删除 xx 并加入 yy,同时将 vv 加入 Against(y)\operatorname{Against}(y)。最后清空 Against(x)\operatorname{Against}(x),并令 xx 的并查集父亲为 yy。这一步把所有原来连接到 xx 的敌对关系改接到 yy,所以三个不变量继续成立。

每次只迁移较小联盟的敌对集合。一个元素被迁移后,它所属联盟的大小至少翻倍,因此每个元素至多迁移 O(logn)\mathcal{O}(\log n) 次。

【复杂度分析】

使用 set 维护敌对集合时,总时间复杂度为

O((n+q)α(n)+mlog2n),\mathcal{O}\bigl((n+q)\alpha(n)+m\log^2 n\bigr),

空间复杂度为 O(n+m)\mathcal{O}(n+m)

【参考代码】

#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;}