CF711D
题意
有 \(n\) 个点和 \(n\) 条边,第 \(i\) 条边从 \(i\) 连到 \(a_i\)。每条边需要指定一个方向(无向边变为有向边)。问有多少种指定方向的方案使得图中不出现环。
题解
\(n\) 个点和 \(n\) 条边。
所以是基环树。
先只考虑单棵基环树的情况。
使得图中不出现环。
基环树环外的边对题目没有影响,既不会影响当前的环,也不会增加新的环。
如果基环树的环上的任意一条边与其他的边的方向不同,就不会产生环。
所以在所有的方案中,只有环上边的方向全部相同的情况是会产生环的。
假设环上边的条数为 \(m\),那么此时的产生环的方案数就是 \(2\times2^{n-m}\) 种,不产生环的方案数就是 \((2^m+2)\times2^{n-m}\) 种。
因为题目不保证图联通,所以考虑基环树森林的情况。
所以对于基环树森林中的每一棵基环树,找出其中的环,都像上面那样计算,将结果相加就是答案。
所以最终的答案是: $$2^{n- \sum m} \coprod{(2^m-2)}$$
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5;int n,m=1e9+7,tot,vis[N],si[N],a[N],b[N],ans=1;
struct edge{int v,nt;}e[N<<1];
int fir[N],cnt;
void add(int u,int v){e[++cnt].v=v;e[cnt].nt=fir[u];fir[u]=cnt;}
int k(int a,int b){int s=1;while(b){if(b&1)(s*=a)%=m;(a*=a)%=m;b>>=1;}return s;}
void dfs(int u,int c){
if(vis[u]){b[tot]=max(b[tot],c-si[u]);return;}
si[u]=c;vis[u]=1;a[tot]++;
for(int i=fir[u];i;i=e[i].nt)dfs(e[i].v,c+1);
}signed main(){
std::ios::sync_with_stdio(false);
cin>>n;for(int i=1,x;i<=n;i++) cin>>x,add(i,x),add(x,i);
for(int i=1;i<=n;i++)
if(!vis[i]) tot++,dfs(i,0);
for(int i=1;i<=tot;i++)
ans=(ans*((k(2,a[i])-k(2,a[i]-b[i]+1))%m)%m+m)%m;
cout<<ans<<endl;return 0;
}