Applese 涂颜色(欧拉降幂,费马小定理降幂详解/**详解*/)

链接:https://ac.nowcoder.com/acm/contest/330/E
来源:牛客网

  • 题目描述
    精通程序设计的 Applese 叕写了一个游戏。
    在这个游戏中,有一个 n 行 m 列的方阵。现在它要为这个方阵涂上黑白两种颜色。规定左右相邻两格的颜色不能相同。请你帮它统计一下有多少种涂色的方法。由于答案很大,你需要将答案对 109+7 取模。
  • 输入描述:
    仅一行两个正整数 n, m,表示方阵的大小。
  • 输出描述:
    输出一个正整数,表示方案数对 109+7 取模。
  • 示例1
    输入
    1 1
    输出
    2
  • 示例2
    输入
    2 2
    输出
    4
  • 备注:1≤n,m≤10100000

  首先我们先看第一列上面的情况,每一个格子有两种(黑色和白色)选择,那么n个格子就是2n种情况,我们就得到第一列有2n种情况,题目又要求相邻两个两格的颜色不能相同,所以第一列一旦确定以后,后面的方格所放的颜色就确定了。最终我们得到的方案数就是2n即使是我们知道了方案数那么解决起来也有一些困难,我们可以看到,这里的n给的很大,即便我们用快速幂去解也是不行的,下面我介绍两种方法来解决这个问题。

  • 费马小定理

如果p是素数,a是正整数,且gcd(a,p)=1,则有ap-1≡1(mod p)

  对于ap-1≡1(mod p)有ap-1≡a0≡1(mod p),说明ax存在循环节,并且此循环节的长度为(p-1),所以可以得到ax%p=ax%(p-1)%p.
大数取余:大数取余我们可以手动模拟算法,过程如下:
Applese 涂颜色(欧拉降幂,费马小定理降幂详解/**详解*/)_第1张图片

#include
#include
#include
#include
#include
#define pi 3.1415926
#define mod 1000000007
using namespace std;

typedef long long LL;
const int Max_n=100005;
char a[Max_n],str[Max_n];

LL Qpow_mod(){//大数取余
	LL ans=0;
	int n=strlen(a);
	for(int i=0;i<n;i++)
		ans=(ans*10+(a[i]-'0'))%(mod-1);
	return ans;
}
LL Qpow(LL a,LL b){
	LL ans=1;
	LL res=a%mod;
	while(b){
		if(b&1) ans=(ans*res)%mod;
		res=(res*res)%mod;
		b>>=1;
	}
	return ans;
}
int main(){ 
	scanf("%s%s",a,str);
	LL b=Qpow_mod();
	printf("%lld\n",Qpow(2,b));
	return 0;
}
  • 欧拉定理(也称费马-欧拉定理)

如果n和a是互素的正整数,则aφ(n)≡1(mod n)

基于对上面费马小定理的循环节,我们可以得到ax%n≡ax%φ(n)%n
欧拉φ函数φ(n):设n是一个正整数。欧拉φ函数φ(n)是不超过n且与n互素的正整数的个数。

  定理:正整数m可以表示为素幂因子分解m=p1k1 * p2k2 * … * prkr,其中p1,p2…pr是不同的素数,则有φ(m)=φ(p1k1) * φ(p2k2)… * φ(prkr)。若p是一个素数,且k是正整数,则有φ(pk)=pk-pk-1

  根据上述定理,我们可以推出来φ(n)。n=p1k1 * p2k2 * … * pmkm,则有φ(m)=φ(p1k1) * φ(p2k2)… * φ(prkr),φ(pk)=pk-pk-1,即 φ(pk)=pk-1 * (p-1)。

#include
#include
#include
#include
#include
#include 
#define pi 3.1415926
#define mod 1000000007
#include
using namespace std;

typedef long long LL;
const int Max_n=100005;//sqrt(mod) 
int prime[Max_n],is_prime[Max_n],j;
char a[Max_n],str[Max_n];

void f(){//埃式筛法(之前的博文中讲过这个问题) 
	for(int i=2;i<=Max_n;i++) is_prime[i]=1;
	for(int i=2;i<=sqrt(Max_n);i++){
		if(is_prime[i]){
			for(int j=i*i;j<=Max_n;j+=i)
				is_prime[j]=0; 
		}
	}
	int j=1;
	for(int i=2;i<=Max_n;i++)
		if(is_prime[i])
			prime[j++]=i;
}

LL phi(LL x){//欧拉函数φ(x) 
	LL ans=1;//最终结果 
	for(int i=1;i<j;i++){
		if(x%prime[i]==0){//存在素因子
			int num=0;//当前素因子的个数 
			while(x%prime[i]==0){
				j++;//如果是当前还是的素数,数量+1 这里应该为num++;
				x/=prime[i];
			} 
			for(int k=1;k<num;k++) ans*=prime[i];//p^(k-1)
			ans*=(x-1);//(p-1) 这里应该修改为prime[i]-1
			if(x==1) break;//是1直接返回ans 
		}
	}
	if(x>1) ans*=(x-1);//x如果是一个素数,φ(x)=x-1;
	return ans; 
}

LL Qpow(LL a,LL b){//快速幂 
	LL ans=1;
	LL res=a%mod;
	while(b){
		if(b&1) ans=(ans*res)%mod;
		res=(res*res)%mod;
		b>>=1;
	}
	return ans;
}

int main(){ 
	f();
	LL mmod=phi(mod); // φ(mod)
	scanf("%s%s",a,str);
	//大数取余 
	LL ans=0;
	int n=strlen(a);
	for(int i=0;i<n;i++)// n% φ(mod)
		ans=(ans*10+(a[i]-'0'))%mmod;
	printf("%lld\n",Qpow(2,ans));//2^(n%φ(mod))%mod 
	return 0;
}

你可能感兴趣的:(Mathematics)