ABC247F - Cards (置换群 DP)

https://atcoder.jp/contests/abc247/tasks/abc247_f

题意:
给出一组卡片,每张卡片有正反两面。正面有1-n的排列,反面也有。让选择一写卡片使得这些卡片两面的数字能凑够1-n。

  • 这种pair的问题要能想到置换群
  • 置换可以分出若干个环,环与环之间直接乘法原理,计算环内的方案。
  • 写成环的形式:(1, 2), (2, 3), (3, 1). 每个数字只在相邻的两个位置出现所以令f[i][1/0]表示第i个卡片取或者不取的方案。但是首尾还有影响,所以整两个f:f1,f2分别表示第一个数字取不取。环内方案就是f1[cnt][1] + f2[cnt][0] + f2[cnt][1]。现在我们发现这个方案是只和环中卡片个数有关。所以与处理出f数组下面用就好。
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false) ,cin.tie(0), cout.tie(0);
//#pragma GCC optimize(3,"Ofast","inline")
#define ll long long
#define PII pair<int, int>
typedef __int128 li;
const int N = 2e5 + 5;
const int M = 1e7 + 5;
const int INF = 0x3f3f3f3f;
const ll LNF = 0x3f3f3f3f3f3f3f3f;
const int mod = 998244353;
const double PI = acos(-1.0);
ll f1[N][2], f2[N][2];
int a[N], b[N]; bool st[N];
int main () {
    IOS
    int n; cin >> n;
    f1[1][0] = 1;
    for ( int i = 2; i <= n; ++ i ) {
        f1[i][0] = (f1[i][0] + f1[i - 1][1]) % mod;
        f1[i][1] = (f1[i][1] + 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][0] + f2[i - 1][1]) % mod;
        f2[i][1] = (f2[i][1] + f2[i - 1][1] + f2[i - 1][0]) % mod;
    }
    for ( int i = 1; i <= n; ++ i ) cin >> a[i];
    for ( int i = 1; i <= n; ++ i ) {
        int x; cin >> x;
        b[a[i]] = x;
    }
    ll res = 1;
    for ( int i = 1; i <= n; ++ i ) {
        if(st[i]) continue;
        int cnt = 0;
        for ( int j = i; !st[j]; j = b[j] ) {
            ++ cnt;
            st[j] = 1;
        }
        if(cnt !=1 ) {
            res = res * (f1[cnt][1] + f2[cnt][0] + f2[cnt][1]) % mod;
        }
    }
    cout << res << "\n";
    return 0;
}
posted @ 2022-04-11 10:55  qingyanng  阅读(59)  评论(0编辑  收藏  举报