P4298 [CTSC2008] 祭祀 题解

P4298 [CTSC2008] 祭祀 题解

给定 DAG,求最长反链长度,最长反链方案,有多少个点可以成为反链上的点。

Case 1

熟知 Dilworth 定理:偏序集的最长反链的长度等于最小链划分。

因为偏序集有传递性,所以我们也需要对 DAG 做一遍传递闭包。

这样可以套用 Dilworth 定理,最小链划分等于点数减去最大匹配数,所以使用二分图最大匹配算法就可以在 \(O(n^3)\) 的时间复杂度内解决这个问题,当然可以用更优的网络流算法,不过没必要。

Case 2

找出最小点覆盖,所有入点和出点都不是最小点覆盖元素的就是一个反链元素。

Step 1

找出最小点覆盖。

考虑如下算法:

从所有非匹配点开始,找到所有一条由非匹配边开始的交错轨,标记交错轨(注意,肯定没法找到增广轨)上的节点。

断言:所有入点未标记和出点被标记的点的并就是最小点覆盖。

证明:

首先它的大小是最小点覆盖的大小,因为一条匹配边要么左右部都未标记要么都被标记,所以大小就是最大匹配的大小,根据 Konig 定理,最大匹配大小等于最小点覆盖大小。

接着证明它覆盖了所有点。

  1. 对于匹配边,左右部标记状态相同,所以一定它被覆盖
  2. 对于非匹配边,如果它左部未标记或右部被标记显然它被覆盖了,否则:
    1. 左部被标记,则右部一定被标记,因为这是一定是一条交错轨的一部分。
    2. 右部未被标记,则左部一定未被标记,否则右部就被访问了。

综上,此方案为一种最小点覆盖。

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

posted @ 2024-04-15 21:24  MoyouSayuki  阅读(20)  评论(0编辑  收藏  举报
:name :name