1 条题解

  • 0
    @ 2023-1-15 15:48:38

    定位为好玩的题。

    简化题意:

    有一张 nn 个点 mm 条边的无重边无自环的有向图,初始时可以选择一个点染黑,其余点均为白点

    若某个点所有入边的起点均为黑点,则该点可以被染黑。最大化图中黑点数量。

    1n2×105,1m5×1051 ≤ ∑n ≤ 2 × 10^5 , 1 ≤ ∑m ≤ 5 × 10^5

    首先可以观察出,若两个点的答案集合有交集,则这两个集合一定是包含关系,因此我们可以尝试将每个点的后继合并到这个点,最后输出最大的集合大小。

    包含关系的证明:

    image-20230114235441236

    暴力合并的复杂度显然是无法接受的,因此考虑如下技巧:

    若该后继的某个前驱已经在当前点的答案集合,则将该前驱直接从后继的前驱集合中删去。否则说明当前点无法被合并,直接退出循环。最后将当前点插入后继的前驱集合。这样可以避免无意义查询,使得点 ii 的遍历次数是 O(ki)O(k_i) 的。

    但是合并后继时貌似有点问题:我们会将后继的后继当成新的后继,若我们构造一个链套菊花图,使得菊花图部分的点都无法被合并但链部分的点都能被合并,则菊花图部分的点会被加入链长次,这样时间复杂度就被卡到了 O(n2)O(n^2)

    解决方案当然是有的,若我们从链顶开始做,就能避免这一情况,因此我们判断若当前点有唯一前驱且前驱编号比自己大(避免在出现环时出错),则不处理当前点,等到处理前驱时再考虑。就可以把复杂度变为 O((n+k)logn)\mathcal{O}((n+\sum k) \log n) 的了。

    还有一个随机化做法,我当时不是很懂,现在也没理解,附在下方有需要的同学可以自己理解下。

    启发式合并std:

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 2e5 + 10;
    struct dsu {
        int fa[N], siz[N];
        inline void init(int k) {
            for (int i = 1; i <= k; ++i) fa[i] = i, siz[i] = 1;
        }
        int find(int k) { return k == fa[k] ? k : fa[k] = find(fa[k]); }
        inline bool merge(int x, int y) {
            x = find(x), y = find(y);
            if (x != y) {
                fa[x] = y, siz[y] += siz[x];
                return true;
            }
            return false;
        }
    } d;
    int deg[N], n, num, t_case;
    set<int> fa[N], to[N];
    bool vis[N];
    int main() {
        scanf("%d", &t_case);
        while (t_case--) {
            scanf("%d", &n);
            for (int i = 1; i <= n; ++i) fa[i].clear(), to[i].clear();
            for (int i = 1; i <= n; ++i) {
                scanf("%d", deg + i);
                for (int j = 1, x; j <= deg[i]; ++j) {
                    scanf("%d", &x);
                    fa[i].insert(x);
                    to[x].insert(i);
                }
            }
            int ans = 0;
            d.init(n);
            for (int i = 1; i <= n; ++i)
                if (d.find(i) == i) {
                    if (fa[i].size() == 1 && *fa[i].begin() > i)
                        continue;
                    auto update = [&](int k) {
                        for (auto it = to[k].begin(); it != to[k].end(); ++it)
                            if (d.find(*it) != *it)
                                it = to[k].erase(it);
                    };
                    update(i);
                    queue<int> q;
                    for (int j : to[i]) q.push(j);
                    while (q.size()) {
                        int p = q.front();
                        q.pop();
                        if (!to[i].count(p))
                            continue;
                        for (auto it = fa[p].begin(); it != fa[p].end();) {
                            if (d.find(*it) == i)
                                it = fa[p].erase(it);
                            else {
                                to[i].insert(p);
                                goto skip;
                            }
                        }
                        d.merge(p, i);
                        to[i].erase(p);
                        update(p);
                        for (int j : to[p])
                            if (i != j)
                                q.push(j), to[i].insert(j);
                    skip:;
                        fa[p].insert(i);
                    }
                    ans = max(ans, d.siz[i]);
                }
            printf("Case #%d: %d\n", ++num, ans);
        }
        return 0;
    }
    
    //Every night that summer just to seal my fate
    //And I scream, "For whatever it's worth
    //I love you, ain't that the worst thing you ever heard?"
    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <cctype>
    #include <vector>
    #include <cmath>
    #include <queue>
    #include <set>
    using namespace std;
    #define ll long long
    #define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
    #define pii pair<int,int>
    #define mp make_pair
    
    char buf[1 << 20], *p1, *p2;
    #define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2)?EOF: *p1++)
    template <typename T> inline void read(T &t) {
    	int v = getchar();T f = 1;t = 0;
    	while (!isdigit(v)) {if (v == '-')f = -1;v = getchar();}
    	while (isdigit(v)) {t = t * 10 + v - 48;v = getchar();}
    	t *= f;
    }
    template <typename T,typename... Args> inline void read(T &t,Args&... args) {
    	read(t);read(args...);
    }
    
    const int N = 2e5 + 10;
    
    int n,f[N],siz[N],cas;
    set <int> G1[N],G2[N];
    int find(int x) {
        return f[x] == x ? x : f[x] = find(f[x]);
    }
    
    void dfs(int u,int v) {
        int fx = find(u),fy = find(v);
        if (fx == fy) return ;
        if (G2[fx].size() > G2[fy].size()) swap(fx,fy);
        vector <pii> edg;
        for (auto y : G2[fx]) {
            G2[fy].insert(y);
            G1[y].erase(fx);
            G1[y].insert(fy);
            if (G1[y].size() == 1) edg.push_back(mp(fy,y));
        }
        f[fx] = fy;
        siz[fy] += siz[fx];
        for (auto p : edg){
            dfs(p.first,p.second);
        }
    }
    
    void solve() {
        read(n);++cas;
        for (int i = 1;i <= n;++i) G1[i].clear(),G2[i].clear(),f[i] = i,siz[i] = 1;
        for (int i = 1;i <= n;++i) {
            int k;read(k);
            for (int j = 1;j <= k;++j) {
                int y;read(y);
                G1[i].insert(y);G2[y].insert(i);
            }
        }
        for (int i = 1;i <= n;++i) {
            if (G1[i].size() == 1) {
                dfs(i,*G1[i].begin());
            }
        }
        int ans = 0;
        for (int i = 1;i <= n;++i) ans = max(ans,siz[i]);
        printf("Case #%d: %d\n",cas,ans);
    }
    
    signed main() {
        int T;read(T);
        while (T--) solve();
    	return 0;
    }
    
    • 1

    信息

    ID
    8
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    (无)
    递交数
    26
    已通过
    1
    上传者