P4298 [CTSC2008] 祭祀 题解
P4298 [CTSC2008] 祭祀 题解
给定 DAG,求最长反链长度,最长反链方案,有多少个点可以成为反链上的点。
Case 1
熟知 Dilworth 定理:偏序集的最长反链的长度等于最小链划分。
因为偏序集有传递性,所以我们也需要对 DAG 做一遍传递闭包。
这样可以套用 Dilworth 定理,最小链划分等于点数减去最大匹配数,所以使用二分图最大匹配算法就可以在 \(O(n^3)\) 的时间复杂度内解决这个问题,当然可以用更优的网络流算法,不过没必要。
Case 2
找出最小点覆盖,所有入点和出点都不是最小点覆盖元素的就是一个反链元素。
Step 1
找出最小点覆盖。
考虑如下算法:
从所有非匹配点开始,找到所有一条由非匹配边开始的交错轨,标记交错轨(注意,肯定没法找到增广轨)上的节点。
断言:所有入点未标记和出点被标记的点的并就是最小点覆盖。
证明:
首先它的大小是最小点覆盖的大小,因为一条匹配边要么左右部都未标记要么都被标记,所以大小就是最大匹配的大小,根据 Konig 定理,最大匹配大小等于最小点覆盖大小。
接着证明它覆盖了所有点。
- 对于匹配边,左右部标记状态相同,所以一定它被覆盖
- 对于非匹配边,如果它左部未标记或右部被标记显然它被覆盖了,否则:
- 左部被标记,则右部一定被标记,因为这是一定是一条交错轨的一部分。
- 右部未被标记,则左部一定未被标记,否则右部就被访问了。
综上,此方案为一种最小点覆盖。
Step 2
根据 Dilworth 定理的证明易知。
Case 3
比上两问思维难度低,我们只需要钦定每个点是反链的元素,然后求出剩余和这个点没有偏序关系的点的最大反链,如果加入此点后是原偏序集最大反链,那么这个点就可以成为最大反链的元素。
参考代码
时间复杂度:\(O(n^{1.5}m + \dfrac{n^3}{w})\sim O( n^2m + n^3)\),瓶颈在二分图最大匹配上。
// Problem: P4298 [CTSC2008] 祭祀
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-04-15 20:34:52
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
// #define int long long
using namespace std;
const int N = 100 + 10, M = 1e3 + 10;
int n, match[N], m, d[N][N], mac;
bool st[N], del[N], mm[N];
vector<int> g[N];
int find(int x) {
if(del[x]) return 0;
for(auto v : g[x]) {
if(st[v] || del[v]) continue;
st[v] = 1;
if(!match[v] || find(match[v]))
return mm[x] = 1, match[v] = x;
}
return 0;
}
bool ok[N * 2];
void dfs(int x) {
if(ok[x]) return ; ok[x] = 1;
for(auto v : g[x]) {
if(ok[v + n]) continue;
ok[v + n] = 1;
if(match[v])
dfs(match[v]);
}
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1, a, b; i <= m; i ++) {
cin >> a >> b;
d[a][b] = 1;
}
for(int k = 1; k <= n; k ++) for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) d[i][j] |= d[i][k] & d[k][j];
for(int i = 1; i <= n; i ++) for(int j = 1; j <= n; j ++) if(d[i][j]) g[i].push_back(j);
mac = n;
for(int i = 1; i <= n; i ++) {
if(find(i)) mac --;
memset(st, 0, sizeof st);
}
cout << mac << '\n';
for(int i = 1; i <= n; i ++)
if(!mm[i]) dfs(i);
for(int i = 1; i <= n; i ++)
cout << (ok[i] && !ok[i + n]); cout << '\n';
for(int i = 1, maa; i <= n; i ++) {
memset(del, 0, sizeof del), memset(match, 0, sizeof match);
maa = n;
for(int j = 1; j <= n; j ++) if(d[j][i] || d[i][j] || j == i) del[j] = 1, maa --;
for(int j = 1; j <= n; j ++) {
if(del[j]) continue;
if(find(j)) maa --;
memset(st, 0, sizeof st);
}
cout << (maa + 1 == mac);
}
return 0;
}