暑假训练DAY25数论初步

数论二·Eular质数筛法

 HihoCoder - 1295 

描述

小Ho:小Hi,上次我学会了如何检测一个数是否是质数。于是我又有了一个新的问题,我如何去快速得求解[1,N]这个区间内素数的个数呢?

小Hi:你自己有什么想法么?

小Ho:有!我一开始的想法是,自然我们已经知道了如何快速判定一个数是否是质数,那么我就直接将[1,N]之间每一个数判定一次,就可以得到结果。但我发现这个方法太笨了。

小Hi:确实呢,虽然我们已经通过快速素数检测将每一次判定的时间复杂度降低,但是N个数字的话,总的时间复杂度依旧很高。

小Ho:是的,所以后来我改变了我的算法。我发现如果一个数p是质数的话,那么它的倍数一定都是质数。所以我建立了一个布尔类型的数组isPrime,初始化都为true。我从2开始枚举,当我找到一个isPrime[p]仍然为true时,可以确定p一定是一个质数。接着我再将N以内所有p的倍数全部设定为isPrime[p*i]=false。

写成伪代码为:

isPrime[] = true
primeCount = 0
For i = 2 .. N
	If isPrime[i] Then
		primeCount = primeCount + 1
		multiple = 2
		While (i * multiple ≤ N)
			isPrime[i * multiple] = false
			multiple = multiple + 1
		End While 
	End If
End For
  

小Hi:小Ho你用的这个算法叫做Eratosthenes筛法,是一种非常古老的质数筛选算法。其时间复杂度为O(n log log n)。但是这个算法有一个冗余的地方:比如合数10,在枚举2的时候我们判定了一次,在枚举5的时候我们又判定了一次。因此使得其时间复杂度比O(n)要高。

小Ho:那有没有什么办法可以避免啊?

小Hi:当然有了,一个改进的方法叫做Eular筛法,其时间复杂度是O(n)的。

提示:Eular质数筛法

×

提示:Eular质数筛法

小Hi:我们可以知道,任意一个正整数k,若k≥2,则k可以表示成若干个质数相乘的形式。Eratosthenes筛法中,在枚举k的每一个质因子时,我们都计算了一次k,从而造成了冗余。因此在改进算法中,只利用k的最小质因子去计算一次k。

首先让我们了解一下Eular筛法,其伪代码为:

isPrime[] = true
primeList = []
primeCount = 0
For i = 2 .. N
	If isPrime[i] Then
		primeCount = primeCount + 1
		primeList[ primeCount ] = i
	End If 
	For j = 1 .. primeCount
		If (i * primeList[j] > N) Then
			Break
		End If
		isPrime[ i * primeList[j] ] = false
		If (i % primeList[j] == 0) Then
			Break
		End If
	End If
End For
	

与Eratosthenes筛法不同的是,对于外层枚举i,无论i是质数,还是是合数,我们都会用i的倍数去筛。但在枚举的时候,我们只枚举i的质数倍。比如2i,3i,5i,...,而不去枚举4i,6i...,原因我们后面会讲到。

此外,在从小到大依次枚举质数p来计算i的倍数时,我们还需要检查i是否能够整除p。若i能够整除p,则停止枚举。

利用该算法,可以保证每个合数只会被枚举到一次。我们可以证明如下命题:

假设一个合数k=M*p1,p1为其最小的质因子。则k只会在i=M,primeList[j]=p1时被筛掉一次。

首先会在i=M,primeList[j]=p1时被筛掉是显然的。因为p1是k的最小质因子,所以i=M的所有质因子也≥p1。于是j循环在枚举到primeList[j]=p1前不会break,从而一定会在i=M,primeList[j]=p1时被筛掉

其次不会在其他时候被筛掉。否则假设k在i=N, primeList[j]=p1时被筛掉了,此时有k=N*p2。由于p1是k最小的质因子,所以p2 > p1,M > N且p|N。则i=N,j枚举到primeList[j]=p1时(没到primeList[j]=p2)就break了。所以不会有其他时候筛掉k。

同时,不枚举合数倍数的原因也在此:对于一个合数k=M*2*3。只有在枚举到i=M*3时,才会计算到k。若我们枚举合数倍数,则可能会在i=M时,通过M*6计算到k,这样也就造成了多次重复计算了。

综上,Eular筛法可以保证每个合数只会被枚举到一次,时间复杂度为O(n)。当N越大时,其相对于Eratosthenes筛法的优势也就越明显。

Close

输入

第1行:1个正整数n,表示数字的个数,2≤n≤1,000,000。

输出

第1行:1个整数,表示从1到n中质数的个数

Sample Input

9

Sample Output

4

说的花里胡哨的,但是线性筛法和埃氏筛法都可以。时间给的很足不差拿一个loglogn

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int size=1e6+5;
bool prime[size];
int p[size],tot;
void solve(int n)
{
	fill(prime,prime+n,true);
	for(int i=2;i<n;i++)
	{
		if(prime[i] ) p[tot++]=i;
		for(int j=0;j<tot&&i*p[j]<n;j++)
		{
			prime[i*p[j]]=false;
			if(i%p[j]==0) break;
		}
	} 
}
int main()
{
	int n;
	scanf("%d",&n);
	solve(n+1);
	printf("%d\n",tot);
}

就这个时间我觉得直接一个一个枚举都行。。。

数论六·模线性方程组

 HihoCoder - 1303 

描述

小Ho:今天我听到一个挺有意思的故事!

小Hi:什么故事啊?

小Ho:说秦末,刘邦的将军韩信带领1500名士兵经历了一场战斗,战死四百余人。韩信为了清点人数让士兵站成三人一排,多出来两人;站成五人一排,多出来四人;站成七人一排,多出来六人。韩信立刻就知道了剩余人数为1049人。

小Hi:韩信点兵嘛,这个故事很有名的。

小Ho:我觉得这里面一定有什么巧妙的计算方法!不然韩信不可能这么快计算出来。

小Hi:那我们不妨将这个故事的数学模型提取出来看看?

小Ho:好!

<小Ho稍微思考了一下>

小Ho:韩信是为了计算的是士兵的人数,那么我们设这个人数为x。三人成排,五人成排,七人成排,即x mod 3, x mod 5, x mod 7。也就是说我们可以列出一组方程:

x mod 3 = 2
x mod 5 = 4
x mod 7 = 6

韩信就是根据这个方程组,解出了x的值。

小Hi:嗯,就是这样!我们将这个方程组推广到一般形式:给定了n组除数m[i]和余数r[i],通过这n组(m[i],r[i])求解一个x,使得x mod m[i] = r[i]。

小Ho:我怎么感觉这个方程组有固定的解法?

小Hi:这个方程组被称为模线性方程组。它确实有固定的解决方法。不过在我告诉你解法之前,你不如先自己想想怎么求解如何?

小Ho:好啊,让我先试试啊!

提示:模线性方程组

×

提示:模线性方程组

小Hi:一开始就直接求解多个方程不是太容易,我们从n=2开始递推:

已知:

x mod m[1] = r[1]
x mod m[2] = r[2]

根据这两个式子,我们存在两个整数k[1],k[2]:

x = m[1] * k[1] + r[1]
x = m[2] * k[2] + r[2]

由于两个值相等,因此我们有:

	m[1] * k[1] + r[1] = m[2] * k[2] + r[2]
=>	m[1] * k[1] - m[2] * k[2] = r[2] - r[1]

由于m[1],m[2],r[1],r[2]都是常数,若令A=m[1],B=m[2],C=r[2]-r[1],x=k[1],y=k[2],则上式变为:Ax + By = C。

是不是觉得特别眼熟。

小Ho:这不是扩展欧几里德么!

小Hi:没错,这就是我们之前讲过的扩展欧几里德

我们可以先通过gcd(m[1], m[2])能否整除r[2]-r[1]来判定是否存在解。

假设存在解,则我们通过扩展欧几里德求解出k[1],k[2]。

再把k[1]代入x = m[1] * k[1] + r[1],就可以求解出x。

同时我们将这个x作为特解,可以扩展出一个解系:

X = x + k*lcm(m[1], m[2]) k为整数

lcm(a,b)表示a和b的最小公倍数。其求解公式为lcm(a,b)=a*b/gcd(a,b)。

将其改变形式为:

X mod lcm(m[1], m[2]) = x。

令M = lcm(m[1], m[2]), R = x,则有新的模方程X mod M = R。

此时,可以发现我们将x mod m[1] = r[1],x mod m[2] = r[2]合并为了一个式子X mod lcm(m[1], m[2]) = x。满足后者的X一定满足前两个式子。

小Ho:每两个式子都可以通过该方法化简为一个式子。那么我们只要重复进行这个操作,就可以将n个方程组化简为一个方程,并且求出一个最后的解了。

小Hi:没错,就是这样。将其写做伪代码为:

M = m[1], R = r[1]
For i = 2 .. N 
	d = gcd(M, m[i])
	c = r[i] - R
	If (c mod d) Then	// 无解的情况
		Return -1
	End If
	(k1, k2) = extend_gcd(M / d, m[i] / d)	// 扩展欧几里德计算k1,k2
	k1 = (c / d * k1) mod (m[i] / d)	// 扩展解系
	R = R + k1 * M		// 计算x = m[1] * k[1] + r[1]
	M = M / d * m[i] 	// 求解lcm(M, m[i])
	R %= M 			// 求解合并后的新R,同时让R最小
End For		
If (R < 0) Then 
	R = R + M
End If
Return R

Close

输入

第1行:1个正整数, N,2≤N≤1,000。

第2..N+1行:2个正整数, 第i+1行表示第i组m,r,2≤m≤20,000,000,0≤r<m。

计算过程中尽量使用64位整型。

输出

第1行:1个整数,表示满足要求的最小X,若无解输出-1。答案范围在64位整型内。

Sample Input

3
3 2
5 3
7 2

Sample Output

23

模线性方程组,直接套模板即可。需要注意因为求的是最小的解所以要每次增加一个约分处理:

(还有一点需要注意,在求公倍数时必须先用一个数再乘另一个数,不然会由于两个数相乘超过了long long而导致错误)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#define md(l,r) (l+r)>>1
#define rson(x) x<<1|1
#define lson(x) x<<1
#define endl '\n'
#define sc(x) scanf("%lld",&x)

using namespace std;
typedef  long long ll;
const int size=1005;
ll m[size],r[size];
ll gcd(ll a, ll b){
     if (b == 0)
         return a;
   return gcd(b, a%b);
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
	if(b==0)
	{
		x=1;
		y=0;
		return a;
	}
	ll r=exgcd(b,a%b,x,y);
	ll temp=y;
	y=x-(a/b)*y;
	x=temp;
	return r; 
}
ll solve(ll &m1,ll m2,ll &r1,ll r2)
{
	ll k1,k2;
	ll d=gcd(m1,m2);
	if((r2-r1)%d) return -1;
	exgcd(m1/d,m2/d,k1,k2);
	//temp=exgcd(m1/temp,m2/temp,k1,k2);
	
	k1=(r2-r1)/d*k1%(m2/d);
	//cout<<k1<<endl;
	r1=k1*m1+r1;
	m1=m1/d*m2;//一定要先除公约数再乘才能得到正确的公倍数 
	r1=r1%m1;
	return r1>0?r1:r1+m1;
}
int main()
{
	ll n;
	while(~scanf("%lld",&n))
	{
		for(ll i=1;i<=n;i++)
		{
			sc(m[i]),sc(r[i]);
		}
		ll m1=m[1],r1=r[1];
		ll ans=0;
		for(ll i=2;i<=n;i++)
		{
			ans=solve(m1,m[i],r1,r[i]);
			if(ans==-1) break;
		}
		cout<<ans<<endl;
	}
	
	return 0;
}

中国剩余定理

 51Nod - 1079 

一个正整数K,给出K Mod 一些质数的结果,求符合条件的最小的K。例如,K % 2 = 1, K % 3 = 2, K % 5 = 3。符合条件的最小的K = 23。

Input

第1行:1个数N表示后面输入的质数及模的数量。(2 <= N <= 10) 
第2 - N + 1行,每行2个数P和M,中间用空格分隔,P是质数,M是K % P的结果。(2 <= P <= 100, 0 <= K < P)

Output

输出符合条件的最小的K。数据中所有K均小于10^9。

Sample Input

3
2 1
3 2
5 3

Sample Output

23

板子题,不解释

#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<stack>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#define md(l,r) (l+r)>>1
#define rson(x) x<<1|1
#define lson(x) x<<1
#define endl '\n'
#define sc(x) scanf("%d",&x)

using namespace std;
typedef long long ll;
int exgcd(int a,int b,ll &x,ll &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;
}
ll crt(ll a[],ll p[],int n)
{
	ll m=1,y,x=0;
	for(int i=0;i<n;i++) m*=p[i];
	for(int i=0;i<n;i++)
	{
		ll w=m/p[i];
		ll tx=0;
		int t=exgcd(p[i],w,tx,y);
		x=(x+y*w*(a[i]/t))%m;
	}
	return (x+m)%m;
}
ll p[15],m[15];
int main()
{
	int n;
	while(~scanf("%d",&n))
	{
		for(int i=0;i<n;i++)
		{
			scanf("%lld%lld",&p[i],&m[i]);
		}
		ll ans=crt(m,p,n);
		cout<<ans<<endl;
	}
	return 0;
}

Reading comprehension

 HDU - 4990 

Read the program below carefully then answer the question. 
#pragma comment(linker, "/STACK:1024000000,1024000000") 
#include <cstdio> 
#include<iostream> 
#include <cstring> 
#include <cmath> 
#include <algorithm> 
#include<vector> 

const int MAX=100000*2; 
const int INF=1e9; 

int main() 

  int n,m,ans,i; 
  while(scanf("%d%d",&n,&m)!=EOF) 
  { 
    ans=0; 
    for(i=1;i<=n;i++) 
    { 
      if(i&1)ans=(ans*2+1)%m; 
      else ans=ans*2%m; 
    } 
    printf("%d\n",ans); 
  } 
  return 0; 
}

Input

Multi test cases,each line will contain two integers n and m. Process to end of file. 
[Technical Specification] 
1<=n, m <= 1000000000

Output

For each case,output an integer,represents the output of above program.

Sample Input

1 10
3 100

Sample Output

1
5

不难推出公式:a(n)=a(n-1)+2*a(n-2)+1观察到这一点之后直接用矩阵连接前一项和后一项的关系,然后再使用矩阵快速幂求解即可

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1100;
int mod;

struct matrix{
	long long a[15][15];
};
 
matrix matrix_muti(matrix a,matrix b,int n)//矩阵相乘(这个只要注意i,j,k的顺序就行了) 
{
	matrix c;
	memset(c.a,0,sizeof(c.a));
	for(int i=1;i<=n;++i)
	   for(int j=1;j<=n;++j)
	      for(int k=1;k<=n;++k){
	      	c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%mod)%mod;
		  }
	return c;
}
 
matrix init(matrix r,int n)//转化单位矩阵 
{
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j)
		   r.a[i][j]=(i==j);
	}
	return r;
}
 
matrix fast_power(matrix a,int n,int k)//快速幂 
{   matrix r;
    r=init(r,n);//先将r.a化为单位矩阵 
	while(k){
	  if(k&1)r=matrix_muti(r,a,n);
	  /*(k&1)与(k&2==1)是一个意思,就是矩阵相乘的时候如果幂次不是偶数,
	  要提出一个“因数”存在r内。又因为无论 k是奇数或者偶数,由于k=k>>1(相当于k=k/2),
	  k最终一定会先变为1再变为0,所以最终结果可以存在r中。*/
	  a=matrix_muti(a,a,n);
	  k=k>>1;
	}
	return r;
}
 


matrix ori;
void init()
{
	ori.a[1][1]=1;
	ori.a[1][2]=1;
	ori.a[1][3]=0;
	ori.a[2][1]=2;
	ori.a[2][2]=0;
	ori.a[2][3]=0;
	ori.a[3][1]=1;
	ori.a[3][2]=0;
	ori.a[3][3]=1;
//	ori.n=3;ori.m=3;
}
int main()
{
	int n;
	init();
	while(~scanf("%d%d",&n,&mod))
	{
		if(n==1)
		{
			cout<<1%mod<<endl;
			continue;
		}
		if(n==2)
		{
			cout<<2%mod<<endl;
			continue;
		}
		
		matrix t=ori;
		matrix p=fast_power(t,3,n-2);
		long long ans=(2*p.a[1][1]%mod+1*p.a[2][1]%mod+1*p.a[3][1]%mod)%mod;
		cout<<ans<<endl;
	}
}

Rikka's Set

 CSU - 2138 

Rikka is poor at math. Now she asks you for help.

A set is known as extraordinary set when the minimum integer of it is equal to its size. 
min{x : x ∈ S}=|S|
For example, S = {3, 7, 8} is extraordinary.

$ g_n $ is the number of extraodinary subsets of {1, 2, ..., n}.

Rikka wants to know the value of $ g_n $.

Input

Input consists of one integer $ n  (1 ≤ n ≤ 10^{18}) $

Output

Output a single integer $ g_n mod 1000000009$

Sample Input

16

Sample Output

987

Hint

经过观察不难发现这就是一个斐波那契数列,为了快,也使用矩阵快速幂:

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const ll mod=1000000009;
struct matrix{
	ll a,b,c,d;
	matrix operator*(const matrix &p)
	{
		matrix temp;
		temp.a=a*p.a%mod+b*p.c%mod;
		temp.b=a*p.b%mod+b*p.d%mod;
		temp.c=c*p.a%mod+d*p.c%mod;
		temp.d=c*p.b%mod+d*p.d%mod;
		return temp;
	}
	matrix(){}
	matrix(ll w,ll x,ll y,ll z) {
		a=w;b=x;c=y;d=z;
	}
};
const matrix E(1,0,0,1);
const matrix ori(1,1,1,0);
matrix quick_pow(matrix x,ll n)
{
	matrix t=E;
	while(n)
	{
		if(n%2)
		t=t*x,--n; 
		n>>=1;
		x=x*x;
	}
	return t;
}
int main()
{
	ll n;matrix t=ori;
	while(~scanf("%lld",&n))
	{
		
		if(n==0) printf("1\n");
		else if(n==1) printf("1\n");
		else if(n==2) printf("1\n");
		else
		{
			matrix x=quick_pow(t,n-2); 
			ll ans=(x.a+x.c)%mod;
			cout<<ans<<endl;
//			if(n%2) printf("%lld\n",((ans*ans)%mod+1)%mod);
//			else printf("%lld\n",(ans*ans)%mod);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/baiyifeifei/article/details/81782315
今日推荐