HDU 6038 Function 找循环节

    这题比赛的时候题意就看了半天,看到最后看懂了,感觉是找循环但是不知道怎么处理,也没再做。比完赛对着标程看才明白这题的做法。

    题意是说有两个数组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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值