AtCoder Beginner Contest 247 F Cards

F - Cards

思路

把每一张牌想象成一条边,牌正面跟反面想象成点。那么这个问题就变成了,从大小为n的边集选出来一些子集合,是的这个子集完全覆盖住所有的点,问这些子集有多少个?

观察到

  • P,Q是一个1~n的排列,所以每个1~n中的每个点总共出现两次,也就是说,每个点都有两条边与其相连
  • 如果选上所有的边,那么1~n构成的图肯定是若干个环,且环上任意一条边只属于一个环,这个第一点已经证明。
  • 所以我们可以单独考虑每个环有多少种方案,再利用乘法原理,把每个环的方案数量相乘即可得到答案。

现在考虑一个包含m个点的环,有多少种选边方案,使得可以完全覆盖这m个点。

考虑动态规划 f [ i ] [ 0 / 1 ] [ 0 / 1 ] ( f [ i ] [ j ] [ k ] ) f[i][0/1][0/1](f[i][j][k]) f[i][0/1][0/1](f[i][j][k])表示总共有i个点 j = 0 / 1 j=0/1 j=0/1表示第一条边没选/选了, k = 0 / 1 k=0/1 k=0/1表示最后一条边选了/没选的方案数量。这里最后一条边指的是 ( 1 , i ) (1,i) (1,i)

  • 边界 f [ 1 ] [ 0 ] [ 0 ] = 0 , f [ 1 ] [ 1 ] [ 1 ] = 1 f[1][0][0] = 0, f[1][1][1] = 1 f[1][0][0]=0,f[1][1][1]=1,只有一个点时,第一条边和最后一条边都是 ( 1 , 1 ) (1,1) (1,1),只有这两条边都被选上,才有一种方案
  • f [ i ] [ j ] [ 0 ] = f [ i − 1 ] [ j ] [ 1 ] f[i][j][0] = f[i-1][j][1] f[i][j][0]=f[i1][j][1]最后一条边不选时,倒数第二条边必须要选上,不然i号点就成为了孤立点。
  • f [ i ] [ j ] [ 1 ] = f [ i − 1 ] [ j ] [ 0 ] + f [ i − 1 ] [ j ] [ 1 ] f[i][j][1] = f[i-1][j][0] + f[i-1][j][1] f[i][j][1]=f[i1][j][0]+f[i1][j][1],最后一条边选上时,倒数第二条边可以选,也可以不选。

所以,i个点构成的环的总方案数量为 f [ i ] [ 0 ] [ 1 ] + f [ i ] [ 1 ] [ 0 ] + f [ i ] [ 1 ] [ 1 ] f[i][0][1] + f[i][1][0] + f[i][1][1] f[i][0][1]+f[i][1][0]+f[i][1][1], 第一条和最后一条都不选时不合法,所以不能计入答案。

记答案为 g [ i ] g[i] g[i], 再代入上面的状态转移方程,发现
g [ i ] = f [ i ] [ 0 ] [ 1 ] + f [ i ] [ 1 ] [ 0 ] + f [ i ] [ 1 ] [ 1 ] g[i] = f[i][0][1] + f[i][1][0] + f[i][1][1] g[i]=f[i][0][1]+f[i][1][0]+f[i][1][1]
= f [ i − 1 ] [ 1 ] [ 1 ] + f [ i − 1 ] [ 0 / 1 ] [ 0 ] + f [ i − 1 ] [ 0 / 1 ] [ 1 ] =f[i-1][1][1]+f[i-1][0/1][0] + f[i-1][0/1][1] =f[i1][1][1]+f[i1][0/1][0]+f[i1][0/1][1]
= ( f [ i − 1 ] [ 1 ] [ 1 ] + f [ i − 1 ] [ 1 ] [ 0 ] + f [ i − 1 ] [ 0 ] [ 1 ] ) + ( f [ i − 1 ] [ 0 ] [ 0 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) =(f[i-1][1][1]+f[i-1][1][0] + f[i-1][0][1])+(f[i-1][0][0]+f[i-1][1][1]) =(f[i1][1][1]+f[i1][1][0]+f[i1][0][1])+(f[i1][0][0]+f[i1][1][1])
= g [ i − 1 ] + ( f [ i − 1 ] [ 0 ] [ 0 ] + f [ i − 1 ] [ 1 ] [ 1 ] ) =g[i-1]+(f[i-1][0][0]+f[i-1][1][1]) =g[i1]+(f[i1][0][0]+f[i1][1][1])
= g [ i − 1 ] + ( f [ i − 2 ] [ 0 ] [ 1 ] + f [ i − 2 ] [ 1 ] [ 0 ] + f [ i − 2 ] [ 1 ] [ 1 ] ) =g[i-1]+(f[i-2][0][1] + f[i-2][1][0] + f[i-2][1][1]) =g[i1]+(f[i2][0][1]+f[i2][1][0]+f[i2][1][1])
= g [ i − 1 ] + g [ i − 2 ] = g[i-1]+g[i-2] =g[i1]+g[i2]

所以我们得到一个更简易的状态转移方程(不化简也没关系,反之不影响答案)
g [ i ] = g [ i − 1 ] + g [ i − 2 ] g[i] = g[i - 1] + g[i - 2] g[i]=g[i1]+g[i2]

有了这个,我们只需要统计每个环有多少个点,然后利用预处理出来的g[i]算出答案

代码

#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N = 2e5 + 10, mod = 998244353;
typedef long long LL;

typedef pair<int,int> PII;

int n, m;
bool multi = false;
int a[N], b[N];
int f[N];

struct Dsu
{
    vector<int> p, sz;
    int n;
    void init(int _n) {
        n = _n;
        p.resize(n + 1);
        sz.resize(n + 1, 0);
        for(int i = 1; i <= n; i++) p[i] = i, sz[i] = 1;
    }
    int find(int x) {
        return p[x] != x ? p[x] = find(p[x]) : p[x];
    }
    void merge(int a, int b) {
        int x = find(a), y = find(b);
        p[x] = y;
        if(x != y) sz[y] += sz[x];
    }
}dsu;


void solve() {
    cin >> n;
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = 0; i < n; i++) cin >> b[i];

    dsu.init(n);
    for(int i = 0; i < n; i++) dsu.merge(a[i], b[i]);

    //f[i]总共i个点
    f[1] = 1, f[2] = 3;
    for(int i = 3; i <= n; i++) {
        f[i] = (f[i - 1] + f[i - 2]) % mod;
    }
    int res = 1;
    
    for(int i = 1; i <= n; i++) {
        if(dsu.find(i) == i) {
            res = (LL)res * (f[dsu.sz[i]]) % mod;
        }
    }
    cout << res << endl;

}


int main()
{
#ifdef ONLINE_JUDGE
#else 
    freopen("F.txt", "r", stdin);
#endif
    int T = 1;
    if(multi) cin >> T;
    while(T--) solve();
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值