• Cards(并查集、环上DP)


    题意

    \(N\)张卡片,编号为\(1, 2, \dots, N\)\(i\)号卡片正面写有\(P_i\),背面写有\(Q_i\)

    在这里,\((P_1, P_2, \dots, P_N)\)\((Q_1, Q_2, \dots, Q_N)\)都是\((1, 2, \dots, N)\)的全排列。

    有多少种方式,选择其中一些卡片,使得\(1, 2, \dots, N\)至少出现过一次。

    题目链接:https://atcoder.jp/contests/abc247/tasks/abc247_f

    数据范围

    \(1 \leq N \leq 2 \times 10^5\)

    思路

    考虑图\(G\),其中\(1,2,\dots, N\)是点,\((P_i, Q_i)\)是边。原始问题可以转述为:有多少边的子集为一个边覆盖。因为每个点的入度、出度都是\(1\),因此图是由若干个环组成。对于每个环,我们需要在相邻两条边中至少选择一条。将每个环的方案数相乘即为最终的答案。

    对于每个环,我们可以使用并查集进行维护。

    首先我们考虑这样一个问题:一个序列中有\(M\)个元素,相邻两个元素中至少选择一个的方案数有多少个?我们可以令\(f_i\)表示前\(i\)个元素的方案数,如果选择第\(i\)个,那么方案数为\(f_{i - 1}\);如果第\(i\)个元素不选,那么第\(i - 1\)个必选,则方案数为\(f_{i - 2}\)。因此\(f_i = f_{i - 1} + f_{i - 2}\)

    回到原来的问题。原来的问题可以转化成:一个环中有\(M\)个元素,相邻两个元素中至少选择一个的方案数有多少个?我们可以另\(g_M\)表示问题的答案。如果选择第\(M\)个元素,就相当于\(M-1\)个元素的序列有多少种选法,方案数为\(f_{M - 1}\);如果不选择第\(M\)个元素,那么第\(1\)个元素和第\(M-1\)个元素必选,就相当于\(M-3\)个元素的序列有多少种选法,方案数为\(f_{M - 3}\)。因此最终答案为\(g_M = f_{M - 1} + f_{M - 3}\)

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    typedef long long ll;
    
    const int N = 200010, mod = 998244353;
    
    int n;
    int a[N], b[N];
    int p[N];
    ll sz[N];
    ll f[N], g[N];
    
    int find(int x)
    {
        if(p[x] != x) p[x] = find(p[x]);
        return p[x];
    }
    
    int main()
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i ++) {
            scanf("%d", &a[i]);
            p[i] = i;
            sz[i] = 1;
        }
        for(int i = 1; i <= n; i ++) scanf("%d", &b[i]);
        for(int i = 1; i <= n; i ++) {
            int pa = find(a[i]), pb = find(b[i]);
            if(pa != pb) {
                p[pa] = pb;
                sz[pb] += sz[pa];
            }
        }
        f[1] = 2, f[2] = 3;
        for(int i = 3; i <= n; i ++) f[i] = (f[i - 1] + f[i - 2]) % mod;
        g[1] = 1, g[2] = 3, g[3] = 4;
        for(int i = 4; i <= n; i ++) g[i] = (f[i - 1] + f[i - 3]) % mod;
        ll ans = 1;
        for(int i = 1; i <= n; i ++) {
            if(p[i] == i) {
                ans = (ans * g[sz[i]]) % mod;
            }
        }
        printf("%lld\n", ans);
        return 0;
    }
    
  • 相关阅读:
    需求层次性、需求分类
    CSMA/CA协议详解
    Git笔记:GitFlow工作流模拟、分支管理、使用规范
    Vue.js笔记(四) 路由router与重定向
    DolphinScheduler 源码分析之 DAG类
    linux 一分钟安装maven linux
    linux 一分钟搭建zookeeper linux 单机版(亲测可用)
    canal-adapter1.1.14最新版本安装的过程中出现的NullPointerException异常
    yum.repos.d中的变量($releasever与$basearch)
    索引知识
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16356287.html
Copyright © 2020-2023  润新知