题目传送门:洛谷 P4298

题意简述:

给定一个 \(n\) 个点,\(m\) 条边的简单有向无环图(DAG),求出它的最长反链,并构造方案。

最长反链:一张有向无环图的最长反链为一个集合 \(S \subseteq V\),满足对于 \(S\) 中的任意两个不同的点 \(u, v \in S\)(\(u \ne v\)),\(u\) 不能到达 \(v\),\(v\) 也不能到达 \(u\),且 \(S\) 的大小尽量大。

题解:

根据 Dilworth 定理,一个 DAG 中最长反链的大小,等于其中最小可重链覆盖大小。

最小可重链覆盖:在 DAG 中选出若干条链,经过每个点至少一次,一个点可被一条链经过多次,且链数尽量少。

其实 Dilworth 定理描述的是:一个偏序集中的最长反链大小,等于其中最小不可重链覆盖大小。

但是因为求出 DAG 的传递闭包后,DAG 也可以表示成偏序集的形式,所以相当于可重链覆盖。

总之先求出 DAG 的传递闭包,然后求形成的偏序集上的最小不可重链覆盖。

那么,最小不可重链覆盖应该怎么求呢?

考虑从每个点自成一条链的形态出发,此时恰好有 \(n\) 条链。

可以发现最终答案一定是合并(首尾相接)若干条链形成的。考虑重新描述这个过程:

对于一个点,它在最终的链上,一定只有最多一个前驱,和最多一个后继。

我们考虑把每个点拆成入点和出点,那么入点和出点应该只能匹配上最多一个点(表示前驱或者后继)。

这似乎是二分图匹配的形式,具体地,我们考虑:

把一个点 \(x\) 拆成两个点:\(x_{out}\) 和 \(x_{in}\),表示出点和入点。

对于一条边 \(x \to y\),连接 \(x_{out}\) 与 \(y_{in}\),表示原图中 \(x\) 的出边指向 \(y\)(这条边是 \(y\) 的入边)。

那么最终形成了一个二分图,左侧是所有 \(x_{out}\),右侧是所有 \(x_{in}\)。而且所有边都是连接左侧的点和右侧的点的。

在这个二分图 \(G = \langle \langle V_{out}, V_{in} \rangle , E' \rangle\) 上做二分图最大匹配:

每一个匹配边 \(x_{out} \leftrightarrow y_{in}\) 都可以还原原图中链的一条边 \(x \to y\)。

每匹配 \(1\) 条边,链的个数就减少 \(1\),则有最小链覆盖的大小等于 \(n\) 减去最大匹配的大小。

继续考虑如何从二分图最大匹配中,构造出最长反链。以下部分参考了 r_64 的题解

我们首先需要构造二分图最大独立集,这部分参考了「图的最大匹配算法」这篇博客:

考虑下图,可以求出它的其中一种最大匹配为 \(\{ \langle 2, D \rangle, \langle 3, E \rangle, \langle 4, A \rangle, \langle 5, C \rangle \}\),设最大匹配大小为 \(m\),这里 \(m = 4\):

从右侧的非匹配点(这里为 \(B\),可能有多个)开始 DFS,右侧的点只能走非匹配边向左访问,左侧的点只能走匹配边向右访问:

可以发现 DFS 到了 \(3, 5, B, C, E\) 这些点。

我们取左侧被 DFS 到的点,以及右侧没被 DFS 到的点,也就是 \(3, 5, A, D\) 这些点,记做集合 \(S\),可以证明 \(S\) 是一个最小点覆盖。

最小点覆盖:选取最少的点,覆盖每条边,也就是说每条边的两个端点至少有一个被选中了。

证明:

  1. 首先有:最小点覆盖等于最大匹配。我们可以证明 \(|S| = m\)。

    这是因为:右侧的非匹配点一定都被 DFS 到了,所以在右侧选取的必然是匹配点。如果一个右侧的匹配点没被选取,即它被 DFS 到了,而这只有可能是因为它在左侧匹配到的点被 DFS 到了,那么左侧匹配到的点就会被选上。即是:每条匹配边的两端点恰好会被选一个。而左侧的非匹配点一定不会被 DFS 到,这是因为如果被 DFS 到了,必然会形成一条交错路(匈牙利算法中的),不满足最大匹配的条件。所以有且仅有匹配边的端点会被选上,而且每条匹配边的两端点恰好被选一个,所以 \(\boldsymbol{|S| = m}\)。

  2. \(S\) 可以覆盖所有的边。

    我们把边按照左右端点是否被 DFS 到,分成 \(2 \times 2 = 4\) 类。那么如果出现了左端点没被 DFS 到,但是右端点被 DFS 到了的边,它才不会被覆盖。然而这是不可能的,这是因为对于一个右侧被 DFS 到的点,与它相连的左侧的点一定都被 DFS 到了。

然后有最大独立集等于最小点覆盖的补集。也就是只要选出左侧没被 DFS 到的点和右侧被 DFS 到的点就行了。

在上图中就是 \(1, 2, 4, B, C, E\) 这 \(6\) 个点。

回到 DAG 的情况(注意到我们举的例子并不是 DAG 导出的二分图,所以这个例子不能用来解释最长反链):

令最大独立集为 \(I\),考虑选出所有 \(x_{out}\) 和 \(x_{in}\) 都属于 \(I\) 的点,记做集合 \(A\),它们构成一个最长反链。

证明:

先证 \(A\) 的确是一个反链:这是容易的,因为任取 \(x \in A\),\(x_{in}\) 就一定是被 DFS 到的点,而 \(x_{out}\) 一定是没被 DFS 到的点,任何两个 \(x, y \in A\) 之间若是有连边就和 DFS 的过程冲突了。

首先有 \(|I| = 2n - |S| = 2n - m\),而 \(|I| - |A|\) 可以看作是满足「\(x_{out}\) 或 \(x_{in}\) 属于 \(I\)」的 \(x\) 的个数,显然这样的 \(x\) 不会超过 \(n\) 个,所以 \(|I| - |A| \le n\),所以 \(|A| \ge |I| - n = n - m\)。

但是 \(A\) 再大,也不能大过 \(n - m\),所以 \(|A| = n - m\),也就是一个最长反链。

总结:只要选出 \(x_{out}\) 没被 DFS 到,且 \(x_{in}\) 被 DFS 到了的点,这些点就组成一个最长反链。

然后是第三问,这只要默认该点被选中,也就是删除这个点和与其有偏序关系的所有点后,再求一次最长反链,如果最长反链的大小只减小了 \(1\),那么这个点就能在最长反链中,否则不能。

下面是代码,复杂度为 \(\mathcal O (n^{3.5})\):

  1. #include <cstdio>
  2. #include <algorithm>
  3. #include <bitset>
  4. namespace Dinic {
  5. const int Inf = 0x3f3f3f3f;
  6. const int MN = 205, MM = 5155;
  7. int N, S, T;
  8. int h[MN], iter[MN], nxt[MM * 2], to[MM * 2], w[MM * 2], tot;
  9. inline void Init(int _N) {
  10. N = _N, tot = 1;
  11. for (int i = 1; i <= N; ++i) h[i] = 0;
  12. }
  13. inline void SetST(int _S, int _T) { S = _S, T = _T; }
  14. inline void ins(int u, int v, int x) { nxt[++tot] = h[u], to[tot] = v, w[tot] = x, h[u] = tot; }
  15. inline void insw(int u, int v, int w1 = Inf, int w2 = 0) {
  16. if (!u) u = S; if (!v) v = T;
  17. ins(u, v, w1), ins(v, u, w2);
  18. }
  19. int lv[MN], que[MN], l, r;
  20. inline bool Lvl() {
  21. for (int i = 1; i <= N; ++i) lv[i] = 0;
  22. lv[S] = 1;
  23. que[l = r = 1] = S;
  24. while (l <= r) {
  25. int u = que[l++];
  26. for (int i = h[u]; i; i = nxt[i])
  27. if (w[i] && !lv[to[i]]) {
  28. lv[to[i]] = lv[u] + 1;
  29. que[++r] = to[i];
  30. }
  31. }
  32. return lv[T] != 0;
  33. }
  34. int Flow(int u, int f) {
  35. if (u == T) return f;
  36. int d = 0, s = 0;
  37. for (int &i = iter[u]; i; i = nxt[i])
  38. if (w[i] && lv[to[i]] == lv[u] + 1) {
  39. d = Flow(to[i], std::min(f, w[i]));
  40. f -= d, s += d;
  41. w[i] -= d, w[i ^ 1] += d;
  42. if (!f) break;
  43. }
  44. return s;
  45. }
  46. inline int DoDinic() {
  47. int Ans = 0;
  48. while (Lvl()) {
  49. for (int i = 1; i <= N; ++i) iter[i] = h[i];
  50. Ans += Flow(S, Inf);
  51. }
  52. return Ans;
  53. }
  54. }
  55. using Dinic::Init;
  56. using Dinic::SetST;
  57. using Dinic::insw;
  58. using Dinic::DoDinic;
  59. using Dinic::h;
  60. using Dinic::nxt;
  61. using Dinic::to;
  62. using Dinic::w;
  63. const int MN = 105;
  64. int N, M, Ans;
  65. std::bitset<101> g[MN];
  66. int match[MN], tagl[MN], tagr[MN];
  67. void DFS(int u) {
  68. tagr[u] = 1;
  69. for (int i = 1; i <= N; ++i)
  70. if (g[i][u] && !tagl[i])
  71. tagl[i] = 1, DFS(match[i]);
  72. }
  73. int main() {
  74. scanf("%d%d", &N, &M);
  75. for (int i = 1; i <= M; ++i) {
  76. int x, y;
  77. scanf("%d%d", &x, &y);
  78. g[x][y] = 1;
  79. }
  80. for (int k = 1; k <= N; ++k)
  81. for (int i = 1; i <= N; ++i)
  82. if (g[i][k]) g[i] |= g[k];
  83. Init(N + N + 2), SetST(N + N + 1, N + N + 2);
  84. for (int i = 1; i <= N; ++i)
  85. insw(0, i, 1), insw(N + i, 0, 1);
  86. for (int i = 1; i <= N; ++i)
  87. for (int j = 1; j <= N; ++j)
  88. if (g[i][j]) insw(i, N + j, 1);
  89. Ans = N - DoDinic();
  90. printf("%d\n", Ans);
  91. for (int i = 1; i <= N; ++i) if (!w[4 * i - 2]) {
  92. for (int j = h[i]; j; j = nxt[j])
  93. if (!w[j]) { match[i] = to[j] - N; break; }
  94. }
  95. for (int i = 1; i <= N; ++i) if (w[4 * i]) DFS(i);
  96. for (int i = 1; i <= N; ++i) printf("%d", !tagl[i] && tagr[i]);
  97. puts("");
  98. for (int u = 1; u <= N; ++u) {
  99. static int del[MN]; int cnt = 0;
  100. for (int i = 1; i <= N; ++i) del[i] = i == u || g[i][u] || g[u][i];
  101. Init(N + N + 2), SetST(N + N + 1, N + N + 2);
  102. for (int i = 1; i <= N; ++i) if (!del[i])
  103. insw(0, i, 1), insw(N + i, 0, 1), ++cnt;
  104. for (int i = 1; i <= N; ++i) if (!del[i])
  105. for (int j = 1; j <= N; ++j) if (!del[j])
  106. if (g[i][j]) insw(i, N + j, 1);
  107. printf("%d", cnt - DoDinic() == Ans - 1);
  108. } puts("");
  109. return 0;
  110. }

洛谷 P4298: bzoj 1143: [CTSC2008]祭祀的更多相关文章

  1. BZOJ 1143: [CTSC2008]祭祀river 最长反链

    1143: [CTSC2008]祭祀river Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline ...

  2. Bzoj 2718: [Violet 4]毕业旅行 && Bzoj 1143: [CTSC2008]祭祀river 传递闭包,二分图匹配,匈牙利,bitset

    1143: [CTSC2008]祭祀river Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1878  Solved: 937[Submit][St ...

  3. bzoj 1143: [CTSC2008]祭祀river / 2718: [Violet 4]毕业旅行 -- 二分图匹配

    1143: [CTSC2008]祭祀river Time Limit: 10 Sec  Memory Limit: 162 MB Description 在遥远的东方,有一个神秘的民族,自称Y族.他们 ...

  4. [BZOJ 1143] [CTSC2008] 祭祀river 【最长反链】

    题目链接:BZOJ - 1143 题目分析 这道题在BZOJ上只要求输出可选的最多的祭祀地点个数,是一道求最长反链长度的裸题. 下面给出一些相关知识: 在有向无环图中,有如下的一些定义和性质: 链:一 ...

  5. 洛谷 P4175: bzoj 1146: [CTSC2008]网络管理

    令人抓狂的整体二分题.根本原因还是我太菜了. 在学校写了一个下午写得头晕,回家里重写了一遍,一个小时就写完了--不过还是太慢. 题目传送门:洛谷P4175. 题意简述: 一棵 \(n\) 个结点的树, ...

  6. BZOJ 1143: [CTSC2008]祭祀river 最大独立集

    题目链接: http://www.lydsy.com/JudgeOnline/problem.php?id=1143 题解: 给你一个DAG,求最大的顶点集,使得任意两个顶点之间不可达. 把每个顶点v ...

  7. BZOJ 1143 [CTSC2008]祭祀river(二分图匹配)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1143 [题目大意] 给出一张有向图,问最大不连通点集,连通具有传递性 [题解] 我们将 ...

  8. BZOJ 1143: [CTSC2008]祭祀river(最大独立集)

    题面: https://www.lydsy.com/JudgeOnline/problem.php?id=1143 一句话题意:给一个DAG(有向无环图),求选出尽量多的点使这些点两两不可达,输出点个 ...

  9. BZOJ 1143: [CTSC2008]祭祀river(二分图最大点独立集)

    http://www.lydsy.com/JudgeOnline/problem.php?id=1143 题意: 思路: 二分图最大点独立集,首先用floyd判断一下可达情况. #include< ...

随机推荐

  1. P1075,P1138(洛谷)

    今天难得做了做洛谷的题,而且还是两个! P1075:已知正整数n是两个不同的质数的乘积,试求出两者中较大的那个质数.输入格式:一个正整数n.输出格式:一个正整数p,即较大的那个质数. 第一版代码: # ...

  2. PHP 1-16课

    浏览器      使用火狐浏览器 认识标签 个人使用的编辑器:HbuilderX 标签是HTML5的基本结构,标签分为双标签和单标签 例如:<li> </li> <br ...

  3. python3操作MySQL的模块pymysql

    本文介绍Python3连接MySQL的第三方库--PyMySQL的基本使用. PyMySQL介绍 PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中 ...

  4. session、cookie、sessionStorage、localStorage的简要理解

    一.cookie和session 首先 session 和 cookie 用于浏览器客户端与服务端数据交互,通过会话的方式跟踪浏览器用户身份. 1.cookie (1).一般由服务器生成,可以设置失效 ...

  5. Linux用户在第一次登录时强制更改初始密码

    迫使用户更改密码 如果你想迫使用户更改其密码,请使用下面这个命令. $ sudo chage -d0 <user-name>   最初,“-d <N>”选项应该被设成密码的“有 ...

  6. 火狐的一个bug

    发现这个bug是因为最近眼睛不太好,所以网页大小都是正常大小的140% 就发现火狐游览器好多网页上的输入框与按钮对不齐 测试代码 <!DOCTYPE html> <html lang ...

  7. Grevl旅游注册的初步界面,以源代码和运行图片展示

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. thinkphp3关闭Runtime中的日志方法

    将LOG_LEVEL允许记录的日志级别设置为空,则不会记录日志

  9. [兴趣使然]用python在命令行下画jandan像素超载鸡

    下午刷煎蛋的时候看到 Dthalo 蛋友发的系列像素超载鸡,就想自己试试用python脚本画一个,老男孩视频里的作业真没兴趣,弄不好吧没意思,往好了写,自己控制不好,能力不够. 所以还是找自己有兴趣的 ...

  10. num10---适配器模式

    1.类适配器 Adapter类,通过继承 被适配的类,实现目标类的接口,完成适配. 分析: java 单继承,所以适配器类 需要继承 被适配类,这就要求目标类必须是接口,有一定局限性. 被适配类的方法 ...