2022牛客暑假多校J.Serval and Essay题解

J.Serval and Essay

题意:给定一张有向图,起初全部点都是白色,你可以选择任意一个点染成黑色,然后经过若干次迭代,每次迭代,对于每个点来说,若当前点的所有来点都是黑色,那么这个点也染成黑色。现在问,找到一个初始点,使得最后的黑色点数最多,输出点数。

知识点:启发式合并

首先需要知道一个结论,我们设 \(S_{x}\) 表示假设我们刚开始染点 \(x\) 最后得到的黑点集合。那么对于 \(\forall u, v \, (u \ne v)\),要么 \(S_{u} \subseteq S_{v}\) 要么 \(S_{u} \cap S_{v} = \empty\)

我尝试下用好理解的方式来解释这点

首先当 \(v \in S_u\) 时,由于染 \(u\) 可以染到 \(v\),所以染 \(v\) 得到的点集 \(S_v\) 一定是 \(S_u\) 的子集。

那么当 \(v \notin S_u\)

我们假设 \(|S_u| \ge |S_v|\) \(S_u \cap S_v = \empty\) 时,满足 \(S_v\) 不是 \(S_u\) 的子集,且两个集合为有序集,即集合可以表示为一个有序数组 \(\{ a_1, a_2, ..., a_{|S_u|}\}\) \(\{ b_1, b_2, ... ,b_{|S_v|}\}\)

既然交集不是空集,那么我们一定可以找到一个最小的 \(k\) 使得 \(b_k \notin S_u\) 于是一定有 \(\{ b_1, b_2, ... b_{k - 1}\} \subseteq S_u\) ,那么根据递推的性质,\(b_k\) 也一定在 \(S_u\) 中出现,矛盾了。刚刚是 \(k \ne 1\) 的情况下,如果是 \(k=1\) 的时候,说明二者无交集。若有交集,则说明存在一个点 \(b_{k'}\) 是能被染成黑色的,那么能到达这个点的所有点都是黑色,也就是说一定满足 \(b_1 \in S_u\),矛盾。

所以上述结论得证,那么知道这个结论之后,我们就想着求出所有点的 \(S\) 集合,然后如果有包含关系,则连边,最后一定得到一个森林,找到森林中最大的连通块,其大小就是答案了。

考虑实现上述所说,我们考虑用两个集合 \(from\)\(to\) 维护每个点的出点和来点

当一个点的 \(from\) 大小只有1时,我们考虑合并两点,按照出点大小启发式合并,将出点少的点合并到多的那个点。如此一直合并,由于使用了启发式合并,每个点最多只会被合并log次,然后set的删和增都是log,总复杂度 \(O(n(log_2n)^2)\)所以这个方法可行

具体实现看代码

不知道是不是牛客机子原因,赛后补题的时候卡我常数了。

#include <bits/stdc++.h>
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL,LL> PLL;
const int INF = 0x3f3f3f3f, N = 2e5 + 10;
const int MOD = 1e9 + 7;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}

set<int> from[N], to[N];
int par[N], sz[N];
queue<PII> q;

int Find(int x) {
    return x == par[x] ? x : par[x] = Find(par[x]);
}
void Merge(int x, int y) {
    x = Find(x), y = Find(y);
    if (x == y) return ;
    if (to[x].size() > to[y].size()) {
        //使用启发式合并
        swap(x, y);
        swap(from[x], from[y]);
    }
    par[x] = y;
    sz[y] += sz[x];
    for (int now : to[x]) {
        int v = Find(now);
        if (v == y) continue;
        from[v].erase(x);
        from[v].insert(y);
        to[y].insert(v);
        if (from[v].size() == 1) q.push({v, *from[v].begin()});
    }
    to[y].erase(x);
}
inline void solve() {
    int n; cin >> n;
    for (int i = 1; i <= n; i ++ ) {
        from[i].clear(), to[i].clear();
        par[i] = i;
        sz[i] = 1;
    }

    for (int i = 1; i <= n; i ++ ) {
        int k; cin >> k;
        while (k -- ) {
            int x; cin >> x;
            from[i].insert(x);
            to[x].insert(i);
        }
    }
    for (int i = 1; i <= n; i ++ ) if (from[i].size() == 1) q.push({i, *from[i].begin()});
    while (!q.empty()) {
        auto t = q.front(); q.pop();
        Merge(t.first, t.second);
    }
    int res = 0;
    for (int i = 1; i <= n; i ++ ) res = max(res, sz[Find(i)]);
    cout << res << endl;
}
signed main() {
#ifdef DEBUG
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
    auto now = clock();
#endif
    ios::sync_with_stdio(false), cin.tie(nullptr);
    cout << fixed << setprecision(2);
    int T; cin >> T;
    for (int i = 1; i <= T; i ++ ) {
        cout << "Case #" << i << ": ";
        solve();
    }
#ifdef DEBUG
    cout << "============================" << endl;
    cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
    return 0;
}
posted @ 2022-07-20 15:56  Time_Limit_Exceeded  阅读(86)  评论(2编辑  收藏  举报