zoukankan      html  css  js  c++  java
  • 【数论】【欧拉函数】【快速幂】洗牌机

    3333: 洗牌机

    时间限制: 2 Sec  内存限制: 512 MB

    题目描述

    2n张牌,放在2n个从12n的有序位置上。洗牌机每次可以把第i张牌洗到pi)的位置上。Pi)的定义如下:

     

    问经过最少多少轮洗牌,才会使所有牌回到原来的位置。

    输入

    输入格式:

    有多组数据,每组数据一个整数n(n<=109)

    输出

    输出格式:

    对于每组数据,输出一个整数,表示答案。

    样例输入

     (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

    1

    样例输出

    2

    提示

    来源

    #----------------------------------------------------------------------------------------------#

    首先,理解这道题我都觉得要疯掉了,什么叫“回到原来的位置”,我连牌都不知道在哪儿,怎么知道它有没有“回到原来的位置”,现在解释一下:回到原来的位置就相当于第1张牌回到了第1个位置。


    极其恶心的题,如果没有“多组数据”,那我们都好说,枚举嘛,枚举第1张牌就可以了:

    #include<cstdio>
    int n;
    int p(int x)
    {
    	if(x<=n) return 2*x;
    	return 2*(x-n)-1;//它的要求就这样
    }
    int main()
    {
    	while(~scanf("%d",&n))
    	{
    		int x=1,ans=0;
    		do//如果不用do-while会直接输出ans,因为x已经赋为1了
    		{
    			x=p(x);
    			ans++;
    		}while(x!=1);//有没有回到1
    		printf("%d
    ",ans);
    	}
    }

    不难理解……也不难超时……


    正解是怎样的呢?以下便是推理过程了(尽管辣眼睛,但我们不畏艰险,还是得慢慢推):

    ①P函数是分段的,显然很麻烦,所以我们机智地把它变为了一个公式:return 2*i%(2*n+1),也就是说:P(i)=2*i%(2*n+1)。

    ②做一次P(i)是2*i%(2*n+1),那么做多次呢?不难得到:Pk(i)=(2^k)*i%(2*n+1),k为次数。

    ③所以,按照枚举的方法,将i假设为1,方便计算(不会影响结果,方程两边同时乘i可以还原),也就是说,要求k为几时,(2^k)%(2*n+1)为1,就是说又回到了原位。

    ④这一步很关键,我们知道欧拉定理:若m>1,(a,m)=1(a,m互质),则a^φ(m)≡1 (mod m),φ(m)还是解释一下,指0-m的整数中与m互质的数的个数,关于证明什么的自己网上搜吧。所以这里就知道了:2和(2*n+1)是互质的(一个奇数一个偶数),所以2^φ(2*n+1)≡1 (mod 2*n+1)。

    ⑤求出φ(2*n+1),然后没完。φ(2*n+1)虽然一定能使原方程成立,但它并不一定是最小的……所以,我们还要枚举φ(2*n+1)的因数(因为它的因数一定也能使原方程成立)。

    ⑥2的乘方要用快速幂哦,每次%(2*n+1)。


    代码:

    #include<cstdio>
    #include<cmath>
    long long n;//被int坑了,所以直接ctrl+f全换long long
    long long skm(long long x,long long n,long long mod)//快速幂
    {
        long long t=1;
        while(n)
        {
            if(n&1) t=(t*x)%mod;
            n>>=1;
            x=x*x%mod;
        }
        return t;
    }
    long long phi(long long x)//求φ
    {
        long long m=(long long)sqrt(x+0.5);
        long long sum=x;
        for(long long i=2;i<=m;i++)
            if(x%i==0)
            {
                sum=sum/i*(i-1);
                while(x%i==0)
                    x/=i;
            }
        return x>1?sum/x*(x-1):sum;
    }
    int main()
    {
        while(~scanf("%lld",&n))
        {
            long long ph=phi(2*n+1);
            long long ans=ph;
            for(long long i=2;i*i<=ph;i++)//枚举到sqrt(ph)
                if(ph%i==0)//如果是因数
                {
                    if(skm(2,i,2*n+1)==1)//i为ph因子
                        if(i<=ans)
                        {
                            ans=i;
                            break;//这里i从小到大枚举,所以只要符合条件就一定为最小了
                        }
                    if(skm(2,ph/i,2*n+1)==1)//ph/i也是ph的因子
                        if(ph/i<=ans)//暂时还不能确定ph/i的大小,所以用ans先比较一下
                            ans=ph/i;
                }
                printf("%lld
    ",ans);
        }
    }

                                                                                                                                                                               By WZY

  • 相关阅读:
    回顾初心
    团队作业6(代码复审+事后诸葛亮)
    Alpha阶段项目复审(鸽牌开发小分队)
    事后诸葛亮(鸽牌开发小分队)
    团队作业5
    第七篇Scrum冲刺博客
    第六篇Scrum冲刺博客
    第五篇Scrum冲刺博客
    第四篇Scrum冲刺博客
    第三篇Scrum冲刺博客
  • 原文地址:https://www.cnblogs.com/LinqiongTaoist/p/7203738.html
Copyright © 2011-2022 走看看