2.6数学问题的解题窍门

2.6

2.6.1

1.求最大公约数

例题:线段上格点的个数

枚举的话的时间复杂度为O(|x2-x1| x |y2-y1|),对坐标的绝对值较大的情况难以处理。其实这道题的答案如page114的图所示,是|x1-x2|和|y1-y2|的最大公约数-1(要注意特判|x1-x2=0且|y1-y2|=0时答案为0).
辗转相除法:设gcd(a,b)是设计子认输a和b的最大公约数的函数,a除b得到的商和余数分别为p和q。因为a=bxp+q,所以gcd(b,q)即整除a又整除b,也就整除gcd(a,b)。反之,因为q=a-bxp,同理可证gcd(a,b)整除gcd(b,q)。因此可以知道gcd(a,b)=gcd(b,a%b)。这样不断操作下去,由于gcd的第二个数总是不断减小的最终会得到gcd(a,b)=gcd(c,0)。0和c的最大公约数为c。

int gcd(int a,int b){
	if(b==0)return a;
	return gcd(b,a%b);
}

复杂度为O(log max(a,b))以内。

例题:双六(扩展欧几里得算法)

这个问题用数学语言表述就是“求整数x和y使得ax+by=1”.可以发现,如果gcd(a,b)!=1,显然无解。反之,如果gcd(a,b)=1,就可以通过扩展原来的辗转相除法来求解事实上,一定存在一堆整数(x,y)使得ax+by=gcd(a,b),并且可以用同样的方法求得。
设int extgod(int a,int b,int & x,int & y)是求解该方程的函数,其返回值是gcd(a,b)。与gcd一样,我们可以递归地定义extgcd。假设已经求得了:
bx’+(a%b)y’=gcd(a,b)
的整数解x’和y’。再将
a%b=a-(a/b)xb
代入后就得到
ay’+b(x’-(a/b)xy’)=gcd(a,b)
而当b=0时则有
ax1+bx0=a=gcd(a,b)
将上述数学语言转换成代码后,就得到如下程序

**扩展欧几里得算法:对于不完全为 0 的非负整数 a,b,若gcd(a,b)表示 a,b 的最大公约数,必然存在整数对x,y ,使得 ax+by = gcd(a,b)。

算法过程:

设 a>b,当 b=0时,gcd(a,b)=a。此时满足ax+by = gcd(a,b)的一组整数解为x=1,y=0;当a*b!=0 时,

设 ax1+by1=gcd(a,b);b*x2+(a mod b)*y2=gcd(b,a mod b);

根据欧几里得原理知 gcd(a,b)=gcd(b,a mod b);则:ax1+by1=b*x2+(a mod b)*y2;

即:ax1+by1=b*x2+(a-(a/b)b)y2=ay2+bx2-(a/b)by2;(a/b:表示a除以b的商)

根据恒等定理得:x1=y2; y1=x2-(a/b)*y2;

按照上面的递归思想,我们不断对gcd进行递归,一定会出现b=0的情况,此时x=1,y=2,递归结束。这样我们就得到了求解一组x1,y1 的方法

int x,y,r; 
void extgcd(int a,int b){
	if(b==0){
		x=1;
		y=0;
		r=a;
	}else{
		extgcd(b,a%b);
		int temp=x;//x2
		x=y;//x1=y2
		y=temp-(a/b)*y;//y1=x2-(a/b)*y2;
	}
}

2.6.2 有关素数的基础算法

所谓素数,是指恰好有两个约数的n的整数(1和他本身),因为n的约束都不超过n,所以只要检查2~n-1的所有整数是否整除n就能判定n是不是素数。在此,如果d时n的约束,那么n/d也是n的约束。由n=d x n/d可以知道min(d,n/d)<=根号n。

例题:区间内素数的个数

区间[a,b)指的是所有满足a<=x<b的整数所构成的集合。
b以内的合数的最小质因数一定不超过根号b。如果根号b以内的素数表的shan话,就可以把埃氏筛法运用到[a,b)上了,也就是说先分别做好[2,根号b)的表和[a,b]的表,然后从[2,根号b)的表中筛得素数得同时,也将其倍数从[a,b)得表中划去,最后只剩下区间[a,b)内的素数了。

typedef long long ll;
bool is_prime[maxl];
bool is_prime_b[maxb];
void segement_sieve(ll a,ll b){
	fill(is_prime,is_prime+(ll)sqrt(b)+5,1);
	fill(is_prime_b,is_prime_b+5+b,1);
	for(int i=2;(ll)i*i<=b;i++){
		if(is_prime[i]){
			for(int j=2*i;(ll)j*j<=b;j+=i)is_prime[j]=false;
			for(ll j=max(2LL,(a+i-1)/i)*i;j<=b;j+=i)is_prime_b[j-a]=false ;
		}
	}
}
例题:Crazy Rows (GCJ 2009 Round2 A)

暂且先考虑最后应该把哪一行放在第一行。最后的第一行应该具有00…0或是10…0的形式。可以交换到第1行的行当然也可以交换到第二及以后的行,当有多个满足条件的行是,选择最佳的行对于的最终费用要小。
确定第一行之后,就没有必要再移动它了,于是对于之后的行就可以以同样的思路处理。
在这道题中每行的0和1并不是那么重要,只要知道每行最后一个1所在的位置就1足够了,先将这些位置预先计算好,那么就能降低行交换时的复杂度。复杂度为O(N2)

int N;
int M[maxn][maxn];
int a[maxn];//最后一个1出现的位置
void solve(){
	int res=0;
	for(int i=0;i<N;i++){
	a[i]=-1;//没有1时为-1
	for(int j=0;j<N;j++){
		if(M[i][j]=='1')a[i]=j;
	}
	for(int i=0;i<N;i++){
		int pos=-1;//要移动到第i行的行
		for(int j=i;j<N;j++){
			if(a[j]<=i){
				pos=i;
				break;
			}
		}
		for(int j=pos;j>i;j--){
			swap(a[j],a[j-1]);
			res++;
		}
	}
	cout << res << endl;
}
例题:2009 Round 1C C

只要不断递归地枚举最初释放地囚犯并计算对应地金币总数,就能求出答案了。

int P,Q,A[MANQ+2];
int dp[MAXQ+1][MAXQ+2];//dp[i][j]表示地是将从A[i]到A[j]号囚犯(不含两端地囚犯)地连续部分里的所有囚犯都是方式,所需地最小金币总数
void solve(){
	A[0]=0;
	A[Q+1]=P+1;
	//为了更方便地处理两端情况,我们把左端当成0号囚犯,右端当作Q+1号囚犯,这样,dp[0][Q+1]就是答案
	for(int i=0;i<=Q;i++){
		dp[i][i+1]=0;
	}
	//从短的区间开始填充dp
	for(int w=2;w<=Q+1;w++){
		for(int i=0;i<W<=Q+1;i++){
			int j=i+w,t=INT_MAX;
			for(int k=i+1;k<j;k++){
				t=min(t,dp[i][k]+dp[k][j]);
			}
			dp[i][j]=t+A[j]-A[i]-2;
		}
	}
	cout << dp[0][Q+1] << endl;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值