数论
取模
有关取模的几个重要式子:
1.(ab)%c = ( (a%c)(b%c) )%c
2.(b/a)%c = b*(a)^(c-2)%c
快速幂
快速幂这个算法是个死算法,理解代码后把他记下来。
快速幂:
int POW(int a,int b){
int ans = 1;
int base = a;
while(b){
if(b & 1) ans *= base;
base *= base;
b >>= 1;
}
return ans;
}
快速幂(取模):
int pow_mod(int a,int b,int c){
int ans = 1;
int base = a%c;
while(b){
if(b & 1) ans = (ans*base)%c;
base = (base*base)%c;
b >>= 1;
}
return ans;
}
GCD/欧几里德算法
gcd即最大公因数,求最大公因数,较为简单的方法辗转相除法, 又名欧几里德算法。用较大数除以较小数,再用出现的余数(第一余数)去除除数,再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。
long long gcd(long long a,long long b){
if(b == 0) return a;
else return gcd(b,a%b);
}
扩展欧几里德
对于两个不全为0的整数a、b,必存在一组解x,y,使得ax+by==gcd(a,b);---------1式
代码实现
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int temp=y;
y=x-(a/b)*y;
x=temp;
return r;
}
由此拓展开ax+by==c,它的解应该是什么呢?x1=x(c/gcd(a,b)),y1=y(c/gcd(a,b))。(其中x,y为1式中解得的x与y)
这个式子的通解x = x1+t*b/d,
y = y1- t*a/d。(t为任意实数)
素数筛
埃氏筛
做法其实很简单,首先将2到n范围内的整数写下来,其中2是最小的素数。将表中所有的2的倍数划去,表中剩下的最小的数字就是3,他不能被更小的数整除,所以3是素数。再将表中所有的3的倍数划去……以此类推,如果表中剩余的最小的数是m,那么m就是素数。然后将表中所有m的倍数划去,像这样反复操作,就能依次枚举n以内的素数,这样的时间复杂度是O(nloglogn)。
代码实现:
bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void isprime(int n)
{
for(int i=2;i<=n;i++)
{
if(!vis[i]) prime[x++]=i;
for(int j=2;j*i<=n;j++)
{
vis[i*j]=true;
}
}
}
欧拉筛
在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。其他复杂度(O(n)).
代码实现
bool vis[maxn] = {0};//数值为0时代表是素数
int prime[maxn] = {0},x = 0;
void oulasai(int n) //欧拉筛
{
for(int i=2;i<=n;i++)
{
if(!vis[i]) prime[x++]=i;
for(int j=0;j<x;j++)
{
if(i*prime[j]>n) break;
vis[i*prime[j]]=true;
if(i%prime[j]==0) break;
}
}
}
例题
A/B
要求(A/B)%9973,但由于A很大,我们只给出n(n=A%9973)(我们给定的A必能被B整除,且gcd(B,9973) = 1)。
Input
数据的第一行是一个T,表示有T组数据。
每组数据有两个数n(0 <= n < 9973)和B(1 <= B <= 10^9)。
Output
对应每组数据输出(A/B)%9973。
Sample Input
2
1000 53
87 123456789
Sample Output
7922
6060
解:
想要写出这一题一定要知道这几个公式,(ab)%m = ( (a%m)(b%m) )%m,(a/b)%m = a*b^(m-2)%m;
所以题目中(A/B)%9973 = A*B^(9971)%9973 = ( (A%9973)(B^(9971)%9973) )%9973 = ( n(B^(9971)%9973) )%9973;
这里需要对B(9971)取模,如果把B(9971)这个先算出来显然不行,会爆,所以就要用到幂取模的算法,这里我使用快速幂取模。
ac代码
#include<cstdio>
#include<iostream>
using namespace std;
int powmod(int a,int b,int c){//快速幂取模
int ans = 1;
int base = a%c;
while(b){
if(b & 1) ans = (ans*base)%c;
base = (base*base)%c;
b >>= 1;
}
return ans;
}
int main(){
int t,n,b,ans;
scanf("%d",&t);
while(t--){
scanf("%d %d",&n,&b);
ans = (n*powmod(b,9971,9973) )%9973;
printf("%d\n",ans);
}
return 0;
}
青蛙的约会
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面。
Input
输入只包括一行5个整数x,y,m,n,L,其中x≠y < 2000000000,0 < m、n < 2000000000,0 < L < 2100000000。
Output
输出碰面所需要的跳跃次数,如果永远不可能碰面则输出一行"Impossible"
Sample Input
1 2 3 4 5
Sample Output
4
解:
我们首先可以假设经过t后相遇,如果他们相遇则会有式子(x+mt)-(y+nt) = kl成立,对该式子进行转换可得(n-m)t+lk = x-y;
这就刚刚好符合扩展欧几里得的式子ax+by = c。
ac代码
#include<cstdio>
#include<iostream>
using namespace std;
long long n,m,x,y,l,xx,yy;
long long a,b,c,d,t;
long long exgcd(long long a, long long b, long long &xx, long long &yy) {
if (b == 0) {
xx = 1;
yy = 0;
return a;
}
long long r = exgcd(b, a%b, xx, yy);
long long t = xx; xx = yy; yy = t - a/b*yy;
return r;
}
int main(){
//long long n,m,x,y,l;
scanf("%lld %lld %lld %lld %lld",&x,&y,&m,&n,&l);
c = x - y;
a = n - m;
b = l;
d = exgcd(a,b,xx,yy);
//printf("%lld %lld\n",xx,yy);
if(c%d!=0){
printf("Impossible\n");
return 0;
}
xx = xx*(c/d);yy = yy*(c/d);
t = xx*d/b;//x = x1+t*b/d,因为t为任意实数所以令t = -t,令x等于0,解出t = x1*d/b。
xx = xx - t*b/d;
if(xx<0) xx+=(b/d);x//xx只能是正数。
printf("%lld",xx);
return 0;
}
美素数
小明对数的研究比较热爱,一谈到数,脑子里就涌现出好多数的问题,今天,小明想考考你对素数的认识。
问题是这样的:一个十进制数,如果是素数,而且它的各位数字和也是素数,则称之为“美素数”,如29,本身是素数,而且2+9 = 11也是素数,所以它是美素数。
给定一个区间,你能计算出这个区间内有多少个美素数吗?
Input
第一行输入一个正整数T,表示总共有T组数据(T <= 10000)。
接下来共T行,每行输入两个整数L,R(1<= L <= R <= 1000000),表示区间的左值和右值。
Output
对于每组数据,先输出Case数,然后输出区间内美素数的个数(包括端点值L,R)。
每组数据占一行,具体输出格式参见样例。
Sample Input
3
1 100
2 2
3 19
Sample Output
Case #1: 14
Case #2: 1
Case #3: 4
解:
按照题目的意思,先用筛法把素数筛出来,随后进行计算个数。但是我们发现这样子会超时,所以我们还需要做一个预处理,用数组s[i],来存放答案,s[i]表示1-i中美素数的个数所以我们知道答案便是s[r]-s[l-1]。
ac代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
#define maxn 1000000
bool vis[maxn+5] = {0};
int prime[maxn+5] = {0},x = 0;
int s[maxn+5] = {0};
void osai(int n){//欧拉筛
int i,j;
for(i = 2;i<=n;i++){
if(!vis[i]) prime[x++] = i;
for(j = 0;j<x;j++){
if(i*prime[j]>n) break;
vis[i*prime[j]] = 1;
if(i%prime[j] == 0) break;
}
}
}
int change(int n){//求各位数之和
int sum = 0;
while(n){
sum+=n%10;
n/=10;
}
return sum;
}
int main(){
int t,l,r,count,i,j;
osai(1000003);
vis[1] = 1;
for(i = 1;i<=1000000;i++){//预处理
s[i] = s[i-1];
if(vis[i] == 0&&vis[change(i)] == 0) s[i]++;
}
scanf("%d",&t);
for(j = 1;j<=t;j++){
count = 0;
scanf("%d %d",&l,&r);
printf("Case #%d: %d\n",j,s[r]-s[l-1]);
}
return 0;
}