这题比赛的时候题意就看了半天,看到最后看懂了,感觉是找循环但是不知道怎么处理,也没再做。比完赛对着标程看才明白这题的做法。
题意是说有两个数组a,b,他们之间满足关系式,比如对于第一组数据,写出关系即可得到
,再通过b的值,可以将f(0)赋值为0,这样f(1)也就等于0,,同理f(0)赋值为1的话,f(1)也就为1。对于f(2),赋值为1或0都是可以的。这样这些方法总结起来,就能知道总共有4种方法。(f(0)=f{1}=f(2)=0;f(0)=f{1}=f(2)=1;f(0)=f{1}=1,f(2)=0;f(0)=f{1}=0,f(2)=1)
因为要求找出相互之间的对应关系的种数,再通过例子,便想着是要找循环来找出对应关系。因为a可以和f函数的顺序构成关系,所以以a为固定,将b一个一个对应进去。比如说假设a0=1,a1=2,a2=3,a3=0,构成一个循环节长度为4的循环,且b0=1,b1=0,构成一个循环节长度为2的循环,先通过a写出式子,就可以得到,这样再将b代进去,因为b循环节为2,所以可以代入为0101或者1010,也就是说b的循环节有多长,可写出的方法就有多少种。
所以最后的结果就等于所有b的循环节的长度乘以有多少这个循环节长度的循环,也就是cal[0][j]*j,最后再乘有多少这个循环节长度的循环就可以了。这里处理的时候可以进行一下优化,对于循环节,a的长度一定要比b长,而且b一定是a的因子,所以如果b不是a的因子可以直接跳过。在跑for循环的时候,以前筛选的方法可以继续使用,就是算出sqrt(i),跑循环只跑到sqrt即可得出一个因子,另一个因子就可直接通过i/j来得到,就不需要全部遍历搜寻一遍了。
找循环节的方法直接用的标程里提供的方法,写一个dfs,从一个点i开始找,之后再找这个点的下一个数,也就是a[i](b[i]),每找过一个点标记一下,最后找到已标记的点则结束遍历,记录下来个数。
下面AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<math.h>
using namespace std;
int a[100005];
int b[100005];
int vis[100005];
int cal[5][100005];
const int mod=1000000007;
int dfs(int t,int i,int *c,int k)
{
if(vis[t])
{
cal[k][i]++;
return 0;
}
vis[t]=1;
dfs(c[t],i+1,c,k);
return 0;
}
int main()
{
int Case=0;
int n,m;
int i,j;
long long ans;
int lim;
long long t;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(vis,0,sizeof(vis));
memset(cal,0,sizeof(cal));
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
for(i=0;i<m;i++)
{
scanf("%d",&b[i]);
}
Case++;
cout<<"Case #"<<Case<<": ";
for(i=0;i<n;i++)
{
if(!vis[i])
dfs(i,0,a,1);
}
memset(vis,0,sizeof(vis));
for(j=0;j<m;j++)
{
if(!vis[j])
dfs(j,0,b,0);
}
ans=1;
for(int i=1;i<=n;i++)
{
lim=(int)sqrt(i+0.5);
t=0;
if(cal[1][i])
{
for(int j=1;j<=lim;j++)
{
if(i%j==0)
{
t+=cal[0][j]%mod*j%mod;
t%=mod;
if(j*j!=i)
t+=cal[0][i/j]%mod*(i/j)%mod;
}
t%=mod;
}
for(int j=1;j<=cal[1][i];j++)
{
ans*=t%mod;
ans%=mod;
}
}
}
cout<<ans<<endl;
}
return 0;
}