欧拉函数 快速幂 扩展欧几里得算法 线性同余方程 吉姆拉尔森计算公式


一 欧拉函数:

主要用处:求1到n中与n互质的数的个数

算法思想:

先对整数n进行分解质因数,然后直接套用公式即可。

输入:3
3 6 8

输出;

2 2 4

代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, null = 0x3f3f3f3f;
int t;
int main()
{
	ios;
	cin>>t;
	while(t--)
	{
		int a;
		cin>>a;
		int res=a;
		for(int i=2;i<=a/i;i++)
		{
			if(a%i==0)//说明i是a的一个质因子
			{
				res=res/i*(i-1);//套用公式
				while(a%i==0) a/=i;//把质因子i处理干净
			}
		}
		if(a>1) res=res/a*(a-1);//a中最多只包含一个大于根号a的质因子,说明a也是一个质因子
		cout<<res<<endl;
	}
	return 0;
}

二 筛法求欧拉函数:

利用线性筛法,类似于筛素数
主要用处:用来求1到n中所有数的欧拉函数之和
在这里插入图片描述

代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, null = 0x3f3f3f3f;
int t;
int a[N],q;//数组a存储所有素数,q表示数组a的下标,代表当前数组中素数的个数
bool vis[N];//标记当前的数是否被划去,false表示没被划去,true表示已经被划去。没有被划去的数为素数
int ou[N];//存储每个数的欧拉函数
ll get_oula(int t)//由于所有数的欧拉函数之和可能会超过int范围,因此采用longlong来存储结果
{
	ou[1]=1;//1的欧拉函数为1
	for(int i=2;i<=t;i++)//枚举2到t之间所有的数
	{
		if(!vis[i])//如果当前这个数没有被划去的话,说明这个数为素数
		{
			a[q++]=i;//将素数存到数组a中,同时q++
			ou[i]=i-1;//素数的欧拉函数等于该素数减去1
		}
		for(int j=0;a[j]<=t/i;j++)//从小到大枚举所有素数,将所有是素数倍数的数都划去
		{
			vis[a[j]*i]=true;
			if(i%a[j]==0)//说明素数a[j]是i的一个质因子
			{
				ou[i*a[j]]=ou[i]*a[j];//这个数的欧拉函数等于i的欧拉函数乘以素数a[j]
				break;//优化
			}
			ou[i*a[j]]=ou[i]*(a[j]-1);//否则这个数的欧拉函数等于i的欧拉函数乘以素数a[j]减去1
		}
	}
	ll res=0;//表示1到t之间所有数的欧拉函数之和
	for(int i=1;i<=t;i++)
	{
		res+=ou[i];
	}
	return res;
}
int main()
{
	ios;
	cin>>t;
	cout<<get_oula(t)<<endl;
	return 0;
}

三 快速幂:

作用: 能够快速的求出a^k mod p的结果,时间复杂度为log(k)。
a k p的范围在10^9以内,不能采用暴力,暴力枚举时间复杂度为k,会超时。
在这里插入图片描述

在这里插入图片描述
输入:
2
3 2 5
4 3 9
输出: 4 1
代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<set>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e3 + 10, null = 0x3f3f3f3f,M=2*N;
const double eps = 1e-6;
int n;
//求出a^k mod p的结果
int quick_mi(int a,int k,int p)//因为数据范围表较大,两个数相乘可能会爆出int,因此采用long long进行存储
{
	int res=1;//表示答案,初始化为1
	while(k)//只要k不等于0,一直进行循环
	{
		if(k&1)  res=(ll)res*a%p;//如果k的二进制表示中最后一位为1的话,则更新res,每个数的结果等于上一个数乘a模p
		k>>=1;//每次将k的最后一位删掉,即看k的二进制表示中下一位数字
		a=(ll)a*a%p;//每次更新一下a,每次a进行平方,
	}
	return res;//返回a的k次方 mod p的结果
}
int main()
{
	scanf("%d",&n);
	while(n--)
	{
		int a,k,p;
		scanf("%d%d%d",&a,&k,&p);
		printf("%d\n",quick_mi(a,k,p));
	}
	return 0;
}

四 扩展欧几里得算法:

在这里插入图片描述

输入:

2

4 6

8 18

输出:

  • 1 1

-2 1

代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, null = 0x3f3f3f3f;
int t;
int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{
	if(b==0)//当b等于0的时候,a与b的最大公因数为a,此时x等于1,y等于0就是一组解
	{
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);//a与b的最大公因数等于b与a%b的最大公因数,同时交换x和y,有利于求出答案
	y-=a/b*x;//y自减a/b*x
	return d;//返回a和b的最大公因数
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		int x,y;
		exgcd(a,b,x,y);//通过引用传递到函数中
		printf("%d %d\n",x,y);
	}
	return 0;
}

五 求解线性同余方程:

在这里插入图片描述

代码实例:

输入:

2

2 3 6

4 3 5

输出:

impossible

-3

代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6 + 10, null = 0x3f3f3f3f;
int t;
int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{
	if(b==0)//当b等于0的时候,a与b最大公因数为a,此时x=1,y=0就是一组解
	{
		x=1,y=0;
		return a;//返回a和b的最大公因数
	}
	int d=exgcd(b,a%b,y,x);//a与b的最大公因数与b和a%b的最大公因数相同,此时交换x和y,有利于求出答案
	y-=a/b*x;//y自减a/b*x
	return d;//返回a和b的最大公因数
}
int main()
{
	scanf("%d",&t);
	while(t--)
	{
		int a,b,m;
		int x,y;
		scanf("%d%d%d",&a,&b,&m);
		int d=exgcd(a,m,x,y);//求出a和m的最大公因数d
		if(b%d) puts("impossible");//如果b不是d的倍数的,说明方程无解
		else printf("%d\n",(ll)x*(b/d)%m);//输出x,此时要注意乘以b/d,因为此时x是a*x+m*y=d的解,等式两边同时乘以b/d即可转化为值为b的解,此外为了避免类型溢出,因此计算过程中采用long long 
	}
	return 0;
}

六 吉姆拉尔森计算公式

注意
函数返回这某一年的某一个月的某一天是星期几,返回值为0到6,其中0表示星期日,1到6表示星期一到星期六。如果月数为1月或者2月的时候,要把它转化为上一年的13月或者14月。

int week(int y,int m,int d)
{
	if(m==1||m==2) m+=12,y--;
	return (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7;
}

题目描述
十三号星期五真的很不常见吗?
每个月的十三号是星期五的频率是否比一周中的其他几天低?
请编写一个程序,计算N年内每个月的13号是星期日,星期一,星期二,星期三,星期四,星期五和星期六的频率。
测试的时间段将会开始于1900年1月1日,结束于1900+N−1年12月31日。
一些有助于你解题的额外信息:
(1) 1900年1月1日是星期一
(2) 在一年中,4月、6 月、9 月、11 月每个月 30 天,2月平年28天,闰年29天,其他月份每个月31天
(3) 公历年份是4的倍数且不是100的倍数的年份为闰年,例如1992年是闰年,1990年不是闰年
公历年份是整百数并且是400的倍数的也是闰年,例如1700年,1800年,1900年,2100年不是闰年,2000年是闰年

输入
共一行,包含一个正整数N
1≤N≤100
输出
共一行,包含七个整数,整数之间用一个空格隔开,依次表示星期六,星期日,星期一,星期二,星期三,星期四,星期五在十三号出现的次数。
样例输入 复制
28
样例输出 复制
49 48 47 49 48 48 47
代码实例:

#include<iostream>
#include<algorithm>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<string>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctype.h>
#include<iomanip>
#include<fstream>
#include<map>
#include<unordered_set>
#include<unordered_map>
using namespace std;
#define endl '\n'
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef long long ll;
const int N = 1e6+10, null = 0x3f3f3f3f;
const double eps=1e-6;
const int P=131;
int n;
int a[8];//存放星期一到星期日每一天的出现的次数
//函数返回这一年的这一月的这一天是星期几,返回值为0到6,其中0表示星期日,1到6表示星期一到星期六
//但是要注意一下,如果月数为1或者2的时候,要把他转化为上一年的13月或者14月,便于计算
int week(int y,int m,int d)
{
	if(m==1||m==2) m+=12,y--;
	return (d+2*m+3*(m+1)/5+y+y/4-y/100+y/400+1)%7;
}
int main()
{	
	ios;
	cin>>n;
	for(int i=1900;i<=1900+n-1;i++)
	{
		for(int j=1;j<=12;j++)
		{
			a[week(i,j,13)]++;
		}
	}
	cout<<a[6]<<' '<<a[0]<<' ';//先输出星期六和星期日一共出现的次数
	for(int i=1;i<=5;i++) cout<<a[i]<<' ';//再依次输出星期1到星期五一共出现的次数
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值