CF1863G

简洁的题面,深邃的思想。

首先,一个经典的套路是:

对于序列中涉及到对于 aaiai 进行操作的问题,一般可以考虑建立 (i,ai) 的内向基环树或者 (ai,i) 的外向基环树转化为图论问题。

我们建立 (i,ai) 的内向基环树,swap(ai,aai)ai=aai,aai=ai。这等价于将 ai 改为自环,然后令 i 的父亲变为 ai 的父亲。

让我们想想,这样操作是很复杂的。

如何简化操作,并且支持快速地查找到某个点的实际父亲?

先不考虑环,一棵树的情况怎么办?

可以对被删除的边打标记,这样在删除了若干边之后,点 x 的实际父亲是往上找的第一条未被标记的边的指向点

而为了对应自环的限制,则点 x 最多只能有一个儿子到它的边被标记。

所以最终每个点有两种情况,第一种是有一个儿子到它的边被标记,第二种是没有。

方案数为 inx+1

那么这棵树的总方案是 i=1n(ini+1)

转为基环树?

可以发现,对于一个环而言,若有且仅有边 (ciri,ciri+1) 没有被标记,也等价于 aciri=ciri。全部都被自环。

并且环不可以被全部标记。

容斥原理,钦定某条边不被标记且其他边都被标记,则方案数是该边端点的入度。

1+i=1numinciri 个方案重复了,且有一个方案不合法。

所以容斥后,环上的贡献是 i=1numincirisumi=1numinciri+11=i=1numincirisumi=1numinciri

然后在把其他不在环上的点 iini+1 乘上去就行了。

对于每一个基环树的方案全部乘起来就好了。

#define int long long
const int p=1e9+7;
#define N 2050500 
vector<int>g;
int dcc,vis[N],c[N],in[N],head[N],flag,tag,ver[N],nxt[N],tot,n,m,a[N],num,cir[N];
void ad(int u,int v){
	nxt[++tot]=head[u],ver[head[u]=tot]=v;
} 
void add(int u,int v){
	ad(u,v);ad(v,u);
}
void pai(int u){
	c[u]=dcc;g.push_back(u);
	for(int i=head[u];i;i=nxt[i]){
		int v=ver[i];
		if(!c[v])pai(v);
	}
}
int get(int u){
	num=0;g.clear();++dcc;pai(u);
	for(auto x:g)vis[x]=0;
	while(!vis[u])vis[u]=1,u=a[u];
	int v=a[u];cir[++num]=u;
	while(v!=u)cir[++num]=v,v=a[v];
	for(auto x:g)vis[x]=0;
	for(int i=1;i<=num;i++)vis[cir[i]]=1;
	int s=0,t=1;
	for(int i=1;i<=num;i++)t=t*(in[cir[i]]+1)%p,s+=in[cir[i]];
	t=(t-s+p)%p;
	for(auto x:g){
		if(!vis[x])t=t*(in[x]+1)%p;
	}
	return t;
}
signed main(){
	ios::sync_with_stdio(false);
	int ans=1,n;cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];in[a[i]]++;add(i,a[i]);
	}
	for(int i=1;i<=n;i++)if(!c[i])ans=ans*get(i)%p;
	ans=(ans+p)%p;
	cout<<ans<<"\n"; 
}
posted @   spdarkle  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示