CF1863G
简洁的题面,深邃的思想。
首先,一个经典的套路是:
对于序列中涉及到对于
和 进行操作的问题,一般可以考虑建立 的内向基环树或者 的外向基环树转化为图论问题。
我们建立
让我们想想,这样操作是很复杂的。
如何简化操作,并且支持快速地查找到某个点的实际父亲?
先不考虑环,一棵树的情况怎么办?
可以对被删除的边打标记,这样在删除了若干边之后,点
而为了对应自环的限制,则点
所以最终每个点有两种情况,第一种是有一个儿子到它的边被标记,第二种是没有。
方案数为
那么这棵树的总方案是
转为基环树?
可以发现,对于一个环而言,若有且仅有边
并且环不可以被全部标记。
容斥原理,钦定某条边不被标记且其他边都被标记,则方案数是该边端点的入度。
有
所以容斥后,环上的贡献是
然后在把其他不在环上的点
对于每一个基环树的方案全部乘起来就好了。
#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";
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!