题意
有\(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;
}