Description
Solution
看到 \(n\) 个点 \(n\) 条边,显然的基环树(可能是基环树森林),所以我们对于环上的点和非环上的点分别处理。
假设一共有 \(cnt\) 个环,每个环上有 \(d_i\) 个点,我们来分类讨论一下:
-
对于环上的点,我们发现只有两种情况会产生环,即
- \(1 \rightarrow 2 \rightarrow 3 \rightarrow ··· \rightarrow d_i - 1 \rightarrow d_i\)
- \(d_i \rightarrow d_i - 1 \rightarrow ··· \rightarrow 3 \rightarrow 2 \rightarrow 1\)
所以我们用总情况数减去 \(2\)即可。方案数:
\[ans = \prod\limits_{i = 1}^{cnt}{(2^{d_i} - 2)} \] -
对于非环上的点,我们发现不论朝哪个方向连,都不会影响是否会产生环,所以方案数为 \(2^{非环上点的个数}\):
\[ans = 2^{n - \sum\limits_{i = 1}^{cnt}d_i} \]
至此,我们就讨论完了(事实上还是很简单的),总结一下:
\[ans = \prod\limits_{i = 1}^{cnt}{(2^{d_i} - 2)} \times 2^{n - \sum\limits_{i = 1}^{cnt}d_i} \]那么如何找环呢?我这里写了个 Tarjan 缩点,大小大于 2 的强连通分量就是环(感觉用牛刀杀鸡了……不管了)。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
const ll N = 2e5 + 10;
ll n;
struct node{
ll v, nxt;
}edge[N];
ll head[N], tot;
ll dfn[N], low[N], tim;
ll stk[N], top, t[N];
ll cnt, siz[N];
inline void add(ll x, ll y){
edge[++tot] = (node){y, head[x]};
head[x] = tot;
}
void tarjan(ll x){
low[x] = dfn[x] = ++tim;
stk[++top] = x;
t[x] = 1;
for(ll i = head[x]; i; i = edge[i].nxt){
ll y = edge[i].v;
if(!dfn[y])tarjan(y), low[x] = min(low[x], low[y]);
else if(t[y]) low[x] = min(low[x], dfn[y]);
}
if(low[x] == dfn[x]){
cnt++;
do{
siz[cnt]++;
t[stk[top--]] = 0;
}while(stk[top + 1] != x);
}
}
inline ll qpow(ll a, ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
signed main(){
scanf("%lld", &n);
for(ll i = 1, x; i <= n; ++i){
scanf("%lld", &x);
add(i, x);
}
for(ll i = 1; i <= n; ++i)
if(!dfn[i]) tarjan(i);
ll sum = 0, ans = 1;
for(ll i = 1; i <= cnt; ++i)
if(siz[i] > 1) ans = ans * (qpow(2, siz[i]) - 2 + mod % mod) % mod, sum += siz[i];
ans = ans * qpow(2, n - sum) % mod;
printf("%lld\n", ans);
return 0;
}