每个人都有三个地址

旧剧院只认门牌,不认故事。

后台有一台很固执的机器。两个人的牌子相同,机器就说他们属于同一组;牌子不同,机器拒绝继续听。

导演每天拿着剧本来,机器每天只回答“同组”或者“不同组”。

一开始,问题很小。两个演员能不能分到同一个休息室。

后来剧本长出了别的要求。甲必须和乙分开。

甲演的角色要压住乙。

乙又要压住丙。丙绕一圈,回头压住甲。

机器仍然只会看牌子。

道具师没有重写机器。他给每个演员多印了几张牌,分别写着“我是谁”“我站在对面”“我压着谁”。

每条新规都被改写成两张或三张牌应该相同。

排练当晚,导演拿着三张牌走进后台。灯一亮,机器终于听懂了剧本。

简化题面

┌─ [P1525] 关押罪犯 / [P2024] 食物链
├─ 平台: 洛谷
├─ P1525: 给出带仇恨值的罪犯关系,求二分分组能承受的最大冲突阈值。
├─ P2024: 给出同类与捕食陈述,统计其中自相矛盾的陈述数量。
├─ 输入: 两题均为关系操作,但参数格式和输出含义不同。
├─ 数据范围: 对象数与操作数较大,需要近乎常数的合并与查询。
├─ 关键限制: 关系复合后仍落在有限个角色类型中。
└─ 完整题面: https://www.luogu.com.cn/problem/P1525;https://www.luogu.com.cn/problem/P2024

机器只会认同组

并查集的原始接口只有两件事:合并两个集合,查询两个对象是否连通。

P1525 的对立关系可以借一张影子牌表达。给对象 x 两个位置:

x      :x 所在的一侧
x + n  :x 的另一侧

要求 x 与 y 分开,就把 x 接到 y 的影子,把 x 的影子接到 y:

union(x, y + n);
union(x + n, y);

某次加入关系后,若 find(x)==find(y),先前的高优先级约束已经把两人推到同一侧,当前关系无法再成立。按仇恨从大到小加入,第一次撞上的权值就是阈值。

两倍空间只是一种结果。第二份节点必须有清楚语义,每条关系必须能改写成连通约束。

三张牌围成一圈

P2024 的关系有三个角色:同类、被我捕食的类、捕食我的类。

每个对象 x 拥有三个位置:

x        :与 x 同类
x + n    :x 捕食的类
x + 2*n  :捕食 x 的类

“x 吃 y”会同时锁定三条关系:

union(x + n,   y);
union(x + 2*n, y + n);
union(x,       y + 2*n);

第一条最容易想到。后两条来自角色循环,少一条,下一次关系复合就会留下缝。

扩展域的关键动作是先命名角色,再沿关系走一圈。数组开几倍,等角色空间定下来才有意义。

牌面增加,户口也要增加

三倍域第一次落码时,初始化仍停在:

for (int i = 1; i <= n; ++i) fa[i] = i;

额外两份地址落进全局数组的零值里。纸面上有三座后台,内存里却都挤在根 0 门下。

改成初始化到 3*n 后,关系才按预期分开。

这是边界错误,也是一种模型验收。表示空间扩展以后,初始化、清空、访问范围、数组容量必须一起扩展。

什么时候别再扩地址

扩展域适合有限角色和稳定复合。二分对立有两个角色,食物链是模三循环。

关系带任意数值,或复合后不断产生新类型,有限份影子牌很快用完。带权并查集、图搜索或其他结构会更合适。

写代码前先问四件事:

  1. 每一份地址代表哪个角色?
  2. 两条关系连续发生,第三条关系怎样推出?
  3. 新事实需要同时合并哪些角色?
  4. 哪种连通状态代表矛盾?

四个答案写在纸上以后,2*n 或 3*n 只是数组长度。

排练散场。后台机器仍然只说“同组”。

导演把三张牌收回剧本夹。演员回家,角色留在门牌背后。


发布信息草案

  • 标题:每个人都有三个地址
  • 摘要:扩展域并查集的关键是把有限关系拆成有语义的角色地址,再将关系复合改写为连通约束。
  • 标签:并查集、扩展域、关系建模、食物链、二分图