暴力枚举算法的优化:抽签问题

来源:互联网 发布:合法网络购彩平台 编辑:程序博客网 时间:2024/04/28 05:03

题目描述

将写有数字的n个纸片放入口袋中,你可以一次从口袋抽取4次纸片,每次记下纸片的数字后将其放回口袋。如果这4个数字的和是m,那么你就赢了,否则你就输了。编写程序,判断当纸片上的数字是k1,k2,…,kn时,是否存在抽取4次和为m的方案。如果存在,输出Yes;否则输出No.

限制条件,数据规模

1<=n<=1000,1<=m<=10^8,1<=ki<=10^8.

时间限制为1s.

输入的第一行表示n,第二行表示m,第三行的n个数字表示k.

样例输入

3

10

1 3 5

3

9

1 3 5

样例输出

Yes

No

编程求解

这属于很经典的枚举问题。刚一拿到这一题目的想法就是用4个for循环暴力枚举所有可能的方案,再判断是否存在k[a]+k[b]+k[c]+k[d]的和为m,存在就输出Yes,否则输出No.这是很直观也很自然的思路,算法的时间复杂度是O(n4).

#include<iostream>using namespace std;const int MAX_N=1000;int main(){freopen("in.txt","r",stdin);int n,m,k[MAX_N];while(cin>>n>>m){for(int i=0;i<n;++i)cin>>k[i];bool flag=false;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)flag=true;}}}}if(flag)cout<<"Yes"<<endl;elsecout<<"No"<<endl;}return 0;}

上面的代码通过测试用例没有问题。但是,只要一提交肯定要超时,因为时间复杂度为n^4,当n取1000的时候,n^4=10^12,超时是一定的,所以需要改进算法。其实算法最核心的地方就是那4个for循环。最内层的for循环所做的事情就是检测在k[n]中是否存在这样一个d,使得

ka+kb+kc+kd=m

其实这个可以换一种方式表达,

kd=m-ka-kb-kc.

也就是说,检查数组k中的所有元素,判断是否有m-ka-kb-kc.其实在一个排序的数组中检查有个很快的检查方法就是二分搜索。所以优化的思路就来了,可以考虑将k先进行一次排序,然后看k最中间的数值:

假设m-ka-kb-kc的值为x,

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

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

再这样递归地进行搜索,最终就可以确定x是否存在。弱存在则输出Yes,否则输出No。

二分搜索算法每次将候选区间缩小至原来的一半,所以算法的时间复杂度就是排序的时间和循环的时间。其中,排序时间为O(nlogn),循环的时间为O(n^3logn),所以最终的时间复杂度为O(n^3logn)。算法实现如下:

#include<iostream>#include<algorithm>using namespace std;const int MAX_N=1000;int n,m,k[MAX_N];bool binary_search(int x){//搜索范围是k[l],k[l+1],...,k[r-1] int l=0,r=n;while(r-l>=1){int mid=(l+r)/2;if(k[mid]==x)return true;else if(k[mid]<x)l=mid+1;elser=mid;}return false;}int main(){freopen("in.txt","r",stdin);while(cin>>n>>m){for(int i=0;i<n;++i)cin>>k[i];sort(k,k+n);bool flag=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]))flag=true;}}}if(flag)cout<<"Yes"<<endl;elsecout<<"No"<<endl;}return 0;}

算法的时间复杂度是O(n^3logn),当n=1000的时候,还是无法满足时间要求,所以算法还需要进一步优化。刚刚关注的是最内层的循环,其实可以目光看得开一点,关注内层的两个循环,内层的两个循环所做的工作就是检查在数组k中是否存在c和d,使得kc+kd=m-ka-kb.

在这种情况下并不能直接使用二分搜索。但是,如果事先枚举出kc+kd所得的n^2个数值后再排序就可以利用二分搜索直接进行查表就可以了。其实,更加准确地说,kc+kd所得的数字去除重复后只有n*(n+1)/2个。

其中排序时间是O(n^2logn),查找时间是O(n^2logn),所以算法的时间复杂度就是O(n^2logn),所以当n=1000时也可以满足要求。AC代码如下:

#include<iostream>#include<algorithm>using namespace std;const int MAX_N=1000;int n,m,k[MAX_N],temp[MAX_N*MAX_N];bool binary_search(int x){//搜索范围是k[l],k[l+1],...,k[r-1] int l=0,r=n*n;while(r-l>=1){int mid=(l+r)/2;if(temp[mid]==x)return true;else if(temp[mid]<x)l=mid+1;elser=mid;}return false;}int main(){freopen("in.txt","r",stdin);while(cin>>n>>m){for(int i=0;i<n;++i)cin>>k[i];//枚举kc+kdfor(int c=0;c<n;++c){for(int d=0;d<n;++d){temp[c*n+d]=k[c]+k[d];}} sort(temp,temp+n*n);bool flag=false;for(int a=0;a<n;++a){for(int b=0;b<n;++b){if(binary_search(m-k[a]-k[b]))flag=true;}}if(flag)cout<<"Yes"<<endl;elsecout<<"No"<<endl;}return 0;}

这题虽然不难,但是思路很值得借鉴,从一个复杂度高的算法出发,逐步优化,不断降低算法的复杂度直到满足要求,这种方式很奏效。

1 0
原创粉丝点击