Cards (置换,dp)

Cards

[Link](F - Cards (atcoder.jp))

题意

​ 给你 n n n个卡片每个第 i i i个卡片正反分别 p i , q i p_i,q_i pi,qi这两个数,对于每个卡片的正面和方面的 ( p 1 , p 1 , . . . , p n ) (p_1,p_1,...,p_n) (p1,p1,...,pn) ( q 1 , q 1 , . . . , q n ) (q_1,q_1,...,q_n) (q1,q1,...,qn)分别是两个 n n n的排列数。

​ 让你从中选择一些卡片并且这些卡片里的所有数包含 1 ∼ n 1\sim n 1n,问你有多少种方案数。

思路

​ 我们从 p i p_i pi连一条到 q i q_i qi的边,这时候图上会形成若干个环,问题转化为从环上选一些边使得每个环上的点都被选中,由于环是独立的符合乘法原理,我们任取一个环,假设包含 m m m个数即含有 m m m个边,问题转化为怎么求这个环的合法方案数?

​ 由于是个环我们不好下手,直接的想法是破环为链,我们按照第一条边选或不选来分类, f [ i ] [ 0 / 1 ] : 表 示 当 前 是 第 i 条 边 , 0 : 不 选 当 前 这 个 边 , 1 : 选 当 前 这 个 边 f[i][0/1]:表示当前是第i条边,0:不选当前这个边,1:选当前这个边 f[i][0/1]i01,转移即 f [ i ] [ 0 ] = f [ i − 1 ] [ 1 ] , f [ i ] [ 1 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] f[i][0]=f[i-1][1],f[i][1]=f[i-1][0]+f[i-1][1] f[i][0]=f[i1][1],f[i][1]=f[i1][0]+f[i1][1],然后分别用 f 1 , f 2 f1,f2 f1,f2表示第一条边选或不选,选的话最后一条边选不选都合法,不选的话最后一条边一定要选。

​ 找环可以用并查集搞。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 998244353;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N], b[N], f[N], sz[N];
int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]);
}
void merge(int x, int y) {
    if (find(x) == find(y)) return ;
    x = find(x), y = find(y);
    f[x] = y;
    sz[y] += sz[x];
}
int f1[N][2], f2[N][2];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i], f[i] = i, sz[i] = 1;
    f1[1][0] = 1;
    for (int i = 2; i <= n; i ++) {
        f1[i][0] = f1[i - 1][1];
        f1[i][1] = ((LL)f1[i - 1][1] + f1[i - 1][0]) % mod;
    }
    f2[1][1] = 1;
    for (int i = 2; i <= n; i ++) {
        f2[i][0] = f2[i - 1][1];
        f2[i][1] = ((LL)f2[i - 1][1] + f2[i - 1][0]) % mod;
    }
    for (int i = 1; i <= n; i ++) {
        int x; cin >> x;
        merge(x, a[i]);
    }
    vector<int> ve; 
    for (int i = 1; i <= n; i ++)
        if (i == f[i]) {
            ve.push_back(sz[i]);
        }
     
    LL res = 1;
    
    for (int i = 0; i < ve.size(); i ++) 
        if (ve[i] > 1)
            res = res * ((LL)f1[ve[i]][1] + f2[ve[i]][0] + f2[ve[i]][1]) % mod;
    
    cout << res << '\n';     
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值