欧拉函数 快速幂 扩展欧几里得算法及线性同于方程
一 欧拉函数:
主要用处:求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;
}