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

J.Serval and Essay

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

知识点:启发式合并

首先需要知道一个结论,我们设 Sx 表示假设我们刚开始染点 x 最后得到的黑点集合。那么对于 u,v(uv),要么 SuSv 要么 SuSv=

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

首先当 vSu 时,由于染 u 可以染到 v,所以染 v 得到的点集 Sv 一定是 Su 的子集。

那么当 vSu

我们假设 |Su||Sv| SuSv= 时,满足 Sv 不是 Su 的子集,且两个集合为有序集,即集合可以表示为一个有序数组 {a1,a2,...,a|Su|} {b1,b2,...,b|Sv|}

既然交集不是空集,那么我们一定可以找到一个最小的 k 使得 bkSu 于是一定有 {b1,b2,...bk1}Su ,那么根据递推的性质,bk 也一定在 Su 中出现,矛盾了。刚刚是 k1 的情况下,如果是 k=1 的时候,说明二者无交集。若有交集,则说明存在一个点 bk 是能被染成黑色的,那么能到达这个点的所有点都是黑色,也就是说一定满足 b1Su,矛盾。

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

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

当一个点的 from 大小只有1时,我们考虑合并两点,按照出点大小启发式合并,将出点少的点合并到多的那个点。如此一直合并,由于使用了启发式合并,每个点最多只会被合并log次,然后set的删和增都是log,总复杂度 O(n(log2n)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 @   Time_Limit_Exceeded  阅读(88)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战

阅读目录(Content)

此页目录为空

点击右上角即可分享
微信分享提示