AtCoder Beginner Contest 247 F - Cards // dp + 并查集


原题链接:F - Cards (atcoder.jp)

题意:

给定N张牌,每张牌正反面各有一个数,所有牌的正面、反面分别构成大小为N的排列P,Q。

求有多少种摆放方式,使得N张牌朝上的数字构成一个1~N的排列。

 

思路:dp + 并查集

  • 建图:有1~N的顶点,然后Pi跟Qi连一条边。

    因为给定的是两个排列,所以每个点的度数为2,因而建出的图必然是由几个独立的环构成。

    根据乘法原理,答案就等于每个环的方案数相乘。

  • 求每个环的方案数:

    假设环的大小为n(点的数量),dp[n]表示这样的环的方案数:dp[1] = 1,dp[2] = 3,当 n >= 3 时,dp[n] = dp[n - 1] + dp[n - 2]。

    上述结论证明:因为每个点都要选出,每个点又跟两条边相连,因此选边时:若此边不选,下条边必须选;若此边已选,下条边可选可不选。那么对于n - 1个点的环,插入第n个点,就由两种情况转移过来。

  • 判环及求环的大小:

    并查集可以很好的完成任务 : )

    (不得不说并查集真的是简洁又好用的数据结构

代码参考:

//Jakon:dp + 并查集
#include 
#define int long long
using namespace std;

const int mod = 998244353;
const int N = 200010;

int n, p[N], q[N], dp[N], fa[N], siz[N];

int find(int x)
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> p[i];
    for(int i = 1; i <= n; i++) cin >> q[i];

    dp[1] = 1, dp[2] = 3;
    for(int i = 3; i <= n; i++) dp[i] = (dp[i - 1] + dp[i - 2]) % mod;

    for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= n; i++) {
        int x = find(p[i]), y = find(q[i]);
        if(x != y) fa[x] = y, siz[y] += siz[x];
    }
    
    int ans = 1;
    for(int i = 1; i <= n; i++) 
        if(fa[i] == i) ans = ans * dp[siz[i]] % mod;
    cout << ans << endl;

    return 0;
}