挑战程序设计竞赛学习笔记5

收获:

不仅要熟练掌握基础算法,丰富的想象力亦是非常重要的

二分查找的复杂度是O(logn)的,即便n变得很大,对数时间的算法依然非常快速。

1.6.3 难度增加的抽签问题

如果把最开始的问题中的n的限制条件改为1<=n<=1000,最初的四重循环O(n的4次方)复杂度的算法显然不够,必须改进算法。

for(int a=0;a<n;a++){
		for(int b=0;b<n;b++){
			for(int c=0;c<n;c++){
				for(int d=0;d<n;d++){
					if(k[a]+k[b]+k[c]+k[d]==m){
						f=true;
					}
				}
			}
		}
	} 

上为最初程序的循环部分。最内侧关于d的循环所做的事情就是

       检查是否有d使得k[a]+k[b]+k[c]+k[d]=m

通过对式子进行移项,就能得到另一种表达方式

       检查是否有d使得k[d]=m-k[a]-k[b]-k[c]

即判断是否有m-k[a]-k[b]-k[c]

根据上述推论,让我们来考虑一下快速检查的方法。

1.二分搜索与O(n³logn)的算法

记所要查找的值m-k[a]-k[b]-k[c]为x。

预先把数组排好序,然后看k中央的数字:

  如果它比x小,x只可能在它的后半段。

  如果它比x大,x只可能在它的前半段。

如果再将上述方法运用到已经减半的x的存在区间上,x的存在区间就变成了初始的1/4.

反复操作,不断缩小x的存在区间,最终确定x存在与否。

二分搜索算法每次将候选区间大致缩小为原来的一半。

因此,要判断长度为n的有序数组k中是否包含x,只要反复执行约log₂n次即可。

二分查找的复杂度是O(logn)的,我们称称这种阶的运行时间为对数时间

即便n变得很大对数时间算法依然非常快速

n11010010001e61e9
log₂n037102030

 

 

 

 将最内侧的循环替换成二分搜索算法之后

排序O(nlogn)

循环O(n³logn)

O(n³logn)比O(nlogn)大,所以这了合起来当作是O(n³logn)

于是我们得到了在O(n³logn)时间内解决的办法

程序:

#include<cstdio>
#include<algorithm>//sort函数头文件。sort(begin,end,cmp),cmp参数可以没有,如果没有默认非降序排序。

using namespace std;
const int MAX_N = 1000;
int n=3,m=10,k[MAX_N]={1,3,5};
//int n=3,m=9,k[MAX_N]={1,3,5};

bool binary_search(int x){
	//x的对象范围是k[l],k[l+1],...,k[r-1]
	int l=0,r=n;
	
	//反复操作直到存在范围为空
	while(r-l>=1){
		int i=(l+r)/2;
		if(k[i]==x) return true;//找到x
		else if(k[i]<x) l=i+1;
		else r=i; 
	} 
	
	//没找到x
	return false;
	 
}

void solve(){
	//为了执行二分查找需要先排序
	sort(k,k+n); 
	//是否找到和为m的组合的标记
	bool f=false;
	
	for(int a=0;a<n;a++){
		for(int b=0;b<n;b++){
			for(int c=0;c<n;c++){
				if(binary_search(m-k[a]-k[b]-k[c])){
					f=true;
				}
			}
		}
	}
	
	//输出到标准输出
	if(f) puts("Yes"); 
	else puts("No");
} 

int main(){
	solve();
	return 0;
}

其实像binary_search这样的函数,多是情况下无需自己实现,可以使用标准实现。

例如C++的STL库就含有提供基本同样功能的函数。

 2.O(n²logn)的算法

但是,将n=1000带入n³logn,会发现这依然是远远不够的,必须进一步优化算法。

刚才我们只着眼于最内侧循环,接下来让我们着眼于内侧的两层循环。

内侧两个循环是在

       检查是否有c和d使得k[c]+k[d]=m-k[a]-k[b]

这种情况不能直接使用二分查找。但是我们可以预先枚举出k[c]+k[d]所得的n²个数字并排好序,再使用二分搜索。

  排序O(n²logn)

  循环O(n²logn)

总时间O(n²logn),这样n=1000也可妥善应对。

程序:

#include<cstdio>
#include<algorithm>//sort函数头文件。sort(begin,end,cmp),cmp参数可以没有,如果没有默认非降序排序。

using namespace std;
const int MAX_N = 1000;
int n=3,m=10,k[MAX_N]={1,3,5};
//int n=3,m=9,k[MAX_N]={1,3,5};

//保存两个数的和序列 
int kk[MAX_N*MAX_N];

bool binary_search(int x){
	//x的对象范围是kk[l],kk[l+1],...,kk[r-1]
	int l=0,r=n*n;
	
	//反复操作直到存在范围为空
	while(r-l>=1){
		int i=(l+r)/2;
		if(kk[i]==x) return true;//找到x
		else if(k[i]<x) l=i+1;
		else r=i; 
	} 
	
	//没找到x
	return false;
	 
}

void solve(){
	//枚举k[c]+k[d]的和
	for(int c=0;c<n;c++){
		for(int d=0;d<n;d++){
			kk[c*n+d]=k[c]+k[d];
		}
	}
	
	//为了执行二分查找需要先排序
	sort(kk,kk+n*n); 
	//是否找到和为m的组合的标记
	bool f=false;
	
	for(int a=0;a<n;a++){
		for(int b=0;b<n;b++){
			if(binary_search(m-k[a]-k[b])){
				f=true;
			}
		}
	}
	
	//输出到标准输出
	if(f) puts("Yes"); 
	else puts("No");
} 

int main(){
	solve();
	return 0;
}

本题既需要二分搜索的基础算法知识,也需要将四个数分成两两考虑的想象力.

像这样从复杂度较高的算法出发,不断降低复杂度直至满足问题要求的过程,也是设计算法时经常会经历的一个过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值