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;
}