Function

Function

题目来源

2017 Multi-University Training Contest - Team 1 - 1006
HDU 6038

题面

描述

给出0到n-1的置换a和0到m-1的置换b
定义从整数0到n-1的集合出发投影到整数0到m-1的集合的映射f。
( 即映射 f:AB
A={xZ0xn1}
B={xZ0xm1}
计算使得对于0到n-1的每一个数,都满足 f(i)=bf(ai) 的映射f有多少个。
两个映射是不同的当且仅当存在至少一个0到n-1的整数在两个映射中投影到两个不同的数。
答案对1e9+7取模。

输入

输入含多组数据。
每组数据中:
第一行有两个数n和m (1≤n≤100000,1≤m≤100000)
第二行有n个0到n-1的数,表示置换a
第三行有m个0到m-1的数,表示置换b
保证 ∑n≤1e6, ∑m≤1e6.

输出

对于每组测试数据,输出一行“Case #x: y”(没有引号),x表示测试数据的编号(从1开始),y表示当前数据的答案

???

将置换群分解为多个循环节

如图所示为长度为3的循环节a和b
f(i)=bf(ai) 表示:
若从a中的节点i出发,依次沿着a边、f边、b边各走一步,可到达b中的节点x,则图中应存在从i到x的f边
如:图中该循环节a的1号节点进行a置换后得到a的2号节点,a的2号节点映射到循环节b中的0号节点,b中的0号节点进行b置换后得到b的1号节点,那么为使该映射f合法,a的1号节点应当可映射到b的1号节点
(即在上图中,若绿边所对应的映射及置换存在,就必存在紫边所对应的映射)

由此
假设循环节a长度为lena 循环节b长度为lenb
用a[1]~a[lena]表示循环节a,将a[1]映射得到的节点标记为b[1],用b[1]~b[lenb]表示循环节b
那么因为a[lena]->a[1]->b[1]->b[2]
所以a[lena]的映射f为b[2]
同理可得:
a[lena-1]->b[3]
a[lena-2]->b[4]
a[lena-3]->b[5]
……
a[1]->b[lena+1]
因为b为循环节,所以b的第lena+1项即b的第(lena+1)%lenb项
所以

b[lena+1]=b[(lena+1)modlenb]=b[1]

lena+11(modlenb)

lena0(modlenb)

因此
对于每一个置换a中的每一个循环节A
设其长度为lena
b中长度lenb为lena约数的循环节B
都可与A构成合法映射
循环节A与B构成的合法映射种类,即lenb
(因为对于循环节A的第一个数,B中的每一个数都可以与之构成映射,而每种对应即为一种最终方案)

最后的最后输出的答案
即对于a的每一个循环节,求出其与b中各循环节构成合法映射的种类数s
a中所有循环节的s相乘即最终的答案

具体实现:
1. 分别计算a和b中每种长度的置换群各有多少个
2. a中长度为i的循环节所能构成的方案数为:对于所有 i%j==0 ,b中长度为j的循环节数目*j(numb[j]*j)
3. a中长度为i的循环节有numa[i]个,因此a中所有长度为i的循环节的总方案数为:长度为i的循环节构成方案数^numa[i]
4. 最终答案为a的各长度循环节构成总方案数的乘积

=另=
人生第一道置换群居然是在比赛打的
想好算法告诉别人别人说再想想通结果我说我先打打看QAQ
结果到我打完都还没有想好_(:з」∠)_
最后二十分钟查错完全放错重点
绝望脸.jpg

明明想通了结果硬是把a循环节相互之间关系全部写成加还查不出的也是没谁了
晚上补题还找不出错又拉了一个别的人听我讲了一遍
“对啊,这里都是对的,那么我们把这些加在一起,就可以得到——欸等等!我为什么要加在一起!”
我可能是个傻子

代码

#include <cmath>
#include <cstdio>
#include <cstring>
#define QAQ 1000000007
const int MAXN=100010;
int n,m;
int numa[MAXN];
long long numb[MAXN];
bool vis[MAXN];
int a[MAXN],b[MAXN];
long long ans;
long long pow(long long x,int n)
{
    long long ans=1;
    while (n)
    {
        if (n&1)
            ans=(ans*x)%QAQ;
        x=(x*x)%QAQ;
        n>>=1;
    }
    return ans;
}
int main()
{
    int kase=0;
    while (scanf("%d%d",&n,&m)==2)
    {
        kase++;
        memset(numa,0,sizeof(numa));
        memset(numb,0,sizeof(numb));
        for (int i=0;i<n;i++)
            scanf("%d",&a[i]);
        for (int i=0;i<m;i++)
            scanf("%d",&b[i]);
        memset(vis,false,sizeof(vis));
        int na=0;
        int maxlen=0;
        for (int i=0;i<n;i++)
        {
            if (vis[i])
                continue;
            int len=1,x=i;
            vis[x]=true;
            while (!vis[a[x]])
            {
                x=a[x];
                vis[x]=true;
                len++;
            }
            numa[len]++;
            if (len>maxlen)
                maxlen=len;
        }
        memset(vis,false,sizeof(vis));
        for (int i=0;i<m;i++)
        {
            if (vis[i])
                continue;
            int len=1,x=i;
            vis[x]=true;
            while (!vis[b[x]])cpp
            {
                x=b[x];
                vis[x]=true;
                len++;
            }
            numb[len]++;
        }
        ans=1;
        for (int i=1;i<=maxlen;i++)
            if (numa[i])
            {
                long long s=0;
                for (int j=1;j<=i;j++)
                    if (numb[j] && i%j==0)
                        s=(s+numb[j]*j%QAQ)%QAQ;
                ans=(ans*pow(s,numa[i]))%QAQ;
            }
        printf("Case #%d: %lld\n",kase,ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值