题目大意
题目保证无论
n
名勇士的排队顺序如何,每名勇士都有星球可选择。
题目分析
其实这就是若干个环加内向树模型。
令
对于树上的所有点,我们直接从叶子向根节点下放即可。具体过程就是一个叶节点对答案贡献为乘上
现在问题变成怎么统计环上的答案。因为题目保证每个勇士都能找到一个星球,因此一个环上一定有至少一条边是不会经过的。我们找出这条边,将其删掉,将环破成链,像在树上一样统计答案即可。
至于怎么找这条边,最暴力的方法自然就是枚举环上每个点暴力下放。我采用并查集维护一段已经全部下放好(
size
都为
1
或者只有一个点)的点集,我们下放时直接向点的并查集父亲跳即可。具体实现请自行脑补,不懂见代码。
当然还有其它奇怪的线性找边方法,这里我就不再累赘了。
总时间复杂度
代码实现
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cctype>
#include <queue>
using namespace std;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch))
{
if (ch=='-') f=-1;
ch=getchar();
}
while (isdigit(ch))
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
const int P=1000000007;
const int N=1000500;
int deg[N],p[N],fa[N],cir[N],size[N],st[N];
int n,ans,p0;
queue<int> q;
bool vis[N];
int getfather(int son){return fa[son]==son?son:fa[son]=getfather(fa[son]);}
void calc()
{
for (int x=p[p0];x!=p0;x=p[x])
{
while (size[x]>1)
{
ans=ans*1ll*size[x]%P;
size[p[x]]+=size[x]-1,size[x]=1;
x=p[x];
}
}
}
void find()
{
for (int x=p[cir[1]];x!=cir[1];x=p[x])
deg[cir[++cir[0]]=x]=0;
for (int i=1,x=cir[1],y,z;i<=cir[0];x=cir[++i])
while (st[x]>1)
{
vis[x]=true;
st[y=getfather(p[x])]+=st[x]-1,st[x]=1;
fa[x]=y,x=y;
}
for (int i=1;i<=cir[0];i++)
if (!vis[cir[i]])
{
p0=cir[i];
break;
}
}
void circle()
{
int x,i,y;
ans=1;
while (!q.empty())
{
x=q.front(),q.pop();
ans=ans*1ll*size[x]%P;
size[p[x]]+=size[x]-1,size[x]=1;
if (!--deg[p[x]])
q.push(p[x]);
}
for (int i=1;i<=n;i++) fa[i]=i,st[i]=size[i];
for (int i=1;i<=n;i++)
if (deg[i])
{
deg[cir[cir[0]=1]=i]=0;
find();
calc();
}
}
int main()
{
freopen("interstellar.in","r",stdin);
freopen("interstellar.out","w",stdout);
n=read();
for (int i=1;i<=n;i++)
size[read()]++;
for (int i=1;i<=n;i++)
deg[p[i]=read()]++;
for (int i=1;i<=n;i++)
if (!deg[i])
q.push(i);
circle();
find();
printf("%d\n",ans);
fclose(stdin);
fclose(stdout);
return 0;
}