• 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;
    }
    
  • 相关阅读:
    bash 中 () {} [] [[]] (()) 的解释
    正则表达式速查笔记
    Makefile速查笔记
    gflags 编译动态库
    在Win10上运行ESXI-Comstomer
    GNU g++常用编译选项用法
    C++标准转换运算符reinterpret_cast
    BZOJ 3211: 花神游历各国【线段树区间开方问题】
    BZOJ 1597: [Usaco2008 Mar]土地购买【斜率优化+凸包维护】
    BZOJ 1046: [HAOI2007]上升序列【贪心+二分状态+dp+递归】
  • 原文地址:https://www.cnblogs.com/miraclepbc/p/16356287.html
Copyright © 2020-2023  润新知