题目描述
三水最近在学习炼丹术。但是众所周知炼丹术是一门危险的学科,需要大量的调参才能保证安全。好在三水在洗衣机里面找到了一张失传已久的图纸,里面记录了若干种材料的药性。这张图纸上记录了 n 种不同的药材,对于每种药材,都需要恰好一种药材来使其稳定 (这种药材可能是其自身,即这种药材本身就很稳定)。三水想知道,通过这张图纸,可以得到多少种不同的稳定的丹方。保证每种药材只会作为稳定剂出现一次。
我们认为一个丹方是从 n 种药材中选择若干种 (不为 0 ),两个丹方被认为是不同的当且仅当存在一种药材在其中一个丹方中且不在另一个中。我们称一个丹方是稳定的,当且仅当所有出现在丹方中的药材的稳定剂也在药材中。
因为输出结果可能很大,所以答案对 998244353 取模。
输入描述
第一行一个数字 n , 表示有 n(1⩽n⩽10^6) 种不同的药材。 接下来一行 n 个数字,第 i 数字 ai(1⩽ai⩽n) 表示药材 i 的稳定剂是 ai,保证输入是 1 到 n 的一个全排列。
输出描述
一个整数 n ,表示答案对 998244353 取模的结果。
样例输入
6
6 2 3 4 5 6 1
样例输出
1
大致思路
本题可以用并查集算法,将有依赖关系的药材都并在一个集合中,意思就是在选择某种药材时,也要一并选择集合中其他的药材,以使丹方稳定。查找有多少个根结点就是有多少个互相独立的集合。在选择丹方时,每个集合都可以选或不选,再去掉全都不选的特殊情况,答案就是2^num-1。
AC代码
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
const int X = 998244353;
int father[N];
int n;
//递归查找根结点,并进行路径压缩
int findFather(int x) {
if(father[x] == x) return x;
int temp = findFather(father[x]);
father[x] = temp;
return temp;
}
int main() {
//解绑缓冲区,加速流输入
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
//初始化father数组,使初始时每个结点都为独立的集合
for(int i = 1; i <= n; i++) {
father[i] = i; //father[i]=i 代表i为并查集根结点
}
int x;
for(int i = 1; i <= n; i++) {
cin >> x;
int fa1 = findFather(i), fa2 = findFather(x); //找到两个根节点,判断是否在一个集合
if(fa1 != fa2) { //若不在同一个集合,合并两个集合
father[i] = x;
}
}
int num = 0; //统计并查集数量
for(int i = 1; i <= n; i++) {
if(father[i] == i) num++;
}
//cout << num;
int ans = 1;
for(int i = 1; i <= num; i++) {
ans = ans * 2 % X;
}
cout << --ans; //去掉全不选的情况
return 0;
}