2017 Multi-University Training Contest - Team 1--1006 Function

Function

 终于把这道题搞会了,有个地方真的很难理解。
考虑置换  aa  的一个循环节,长度为  ll  ,那么有 $f(i) = b_{f(a_i)} = b_{b_{f(a_{a_i})}} = \underbrace{b_{\cdots b_{f(i)}}}_{l\text{ times }b}$ 。

那么 f(i)f(i) 的值在置换 bb 中所在的循环节的长度必须为 ll 的因数。

而如果 f(i)f(i) 的值确定下来了,这个循环节的另外 l - 1l1 个数的函数值也都确定下来了。

答案就是 \sum_{i = 1}^{k} \sum_{j | l_i} {j \cdot cal_j}i=1kjlijcalj 改为 \prod_{i = 1}^{k} \sum_{j | l_i} {j \cdot cal_j}i=1kjlijcalj ,其中 kk 是置换 aa 中循环节的个数, l_ili 表示置换 aa 中第 ii 个循环节的长度, cal_jcalj 表示置换 bb 中长度为 jj 的循环节的个数。

时间复杂度是 $\mathcal{O}(n + m)$ 。

  一直不明白为什么a中循环节的长度为b中循环节的长度的倍数才符合条件。这个函数确实很难理解,当时和队友讨论的时候把两个数组合在一起讨论循环节,却忽略了单个数组里面也存在循环节,而且还符合某些性质。做多校的时候大家都被1002坑住了,到后面没什么时间写这个题,对比一下才发现差距好大,做比赛跟休闲似的。。。。
   循环节直接dfs或者暴力就可以预处理出来。
const int N=1e5+10;
int n,m,a[N],b[N],vis[N];
ll numa[N],numb[N],la[N];
void init()
{
    memset(la,0,sizeof(la));
    memset(vis,0,sizeof(vis));
    memset(numa,0,sizeof(numa));
    memset(numb,0,sizeof(numb));
}
int main()
{
    int t=1;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=0; i<n; i++) scanf("%d",&a[i]);
        for(int i=0; i<m; i++) scanf("%d",&b[i]);
        ll ans=1;
        int ma1=0,ma2=0,cnt=0;
        for(int i=0; i<m; i++)
            if(!vis[b[i]])
            {
                int num=1,pre=b[b[i]];
                vis[b[i]]=1;
                while(!vis[pre])
                {
                    num++;
                    vis[pre]=1;
                    pre=b[pre];
                }
                numb[num]++;//长度为num的个数
                ma2=max(num,ma2);
            }
        memset(vis,0,sizeof(vis));
        for(int i=0; i<n; i++)
            if(!vis[a[i]])
            {
                int num=1,pre=a[a[i]];
                vis[a[i]]=1;
                while(!vis[pre])
                {
                    num++;
                    vis[pre]=1;
                    pre=a[pre];
                }
                numa[cnt++]=num;//长度为num是否出现过
                la[num]=-1;
                ma1=max(num,ma1);
            }
        for(int i=1; i<=ma2; i++)
            if(numb[i])
            {
                ll tmp=numb[i],id=i;
                for(int j=i; j<=ma1; j+=i)
                    if(la[j])
                    {
                        if(la[j]==-1) la[j]=0;
                        la[j]+=id*tmp;
                    }
            }
         for(int i=1; i<=ma1; i++) if(la[i]<0) la[i]=0;
        for(int i=0; i<cnt; i++)
        ans=ans*la[numa[i]]%MOD;
        printf("Case #%d: %lld\n",t++,ans);
    }
    return 0;
}

/*

3 3
0 1 2
0 1 2

3 2
0 1 2
1 0

*********
27

0
*********
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值