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;
}
posted @ 2022-06-08 16:50  pbc的成长之路  阅读(160)  评论(0)    收藏  举报