你的朋友提议玩一个游戏:将写有数字的n 个纸片放入口袋中,你可以从口袋中抽取4 次纸
片,每次记下纸片上的数字后都将其放回口袋中。如果这4 个数字的和是m,就是你赢,否
则就是你的朋友赢。你挑战了好几回,结果一次也没赢过,于是怒而撕破口袋,取出所有纸
片,检查自己是否真的有赢的可能性。请你编写一个程序,判断当纸片上所写的数字是k1,
k2, …, kn 时,是否存在抽取4 次和为m 的方案。如果存在,输出Yes;否则,输出No。
限制条件
1 ≤ n ≤ 50
1 ≤ m ≤ 108
1 ≤ ki ≤ 108
-----------------------------------------------------------------------------------------------------------------------
穷举发:
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]
就是说,检查数组k中所有元素,判断是否有m - k[a] - k[b] - k[c] 这个数值的元素。
让我们着眼这一点来考虑快速的检查方法。使用名为二分搜索的算法。
二分法:
记所要查找的值m - k[a] - k[b] - k[c]为x。要预先把数组k排好序,然后看k中央的数字,可知:
如果它比x小,x只可能在它的后面半段。
如果它比x大,x只可能在它的前面半段。
如果再将上述方法运用在已经减半的x的存在区间上,x的存在区间就变成了初始的1/4。这样反复
操作就可以不断缩小x的存在区间,最终可以确定x存在与否。
//假设我们的数据已经进行了排序(从小到大的顺序)
#include <stdio.h>
#define MAX_N 100
/*
x:要求抽签的数值
n:数据的个数
a:存放数据的数组
*/
static int search(int x, int n, int a[]);
int main(int argc, char** argv)
{
int n;
int m;
int a[MAX_N];
printf("input n:");
scanf("%d", &n);
printf("input m:");
scanf("%d", &m);
printf("input your array\n");
int i = 0;
for(i=0;i<n;i++)
{
scanf("%d", &a[i]);
}
//three loops
int j = 0;
int k = 0;
for(i=0;i<n;i++)
{
for(j=0;j<n;j++)
{
for(k=0;k<n;k++)
{
int x = m - a[i] - a[j] - a[k];
if( search(x, n, a) )
{
printf("%d=%d+%d+%d+%d\n",m,x,a[i],a[j],a[k]);
}
}
}
}
printf("end\n");
return 0;
}
static int search(int x, int n, int a[])
{
/*
注意 l 跟 n 的取值的变化
l:判断区间的 数组的“起始下标值”
n:判断区间的数组的截止的“位置下标+1”
*/
int l = 0;
int r = n;
while(r-l>=1)//这个区间中存在数值
{
int i = (l + r)/2;
if(x == a[i])
{
printf( "yes\n" );
return 1;
}
else if(x > a[i])
{
l = i + 1;
}
else
{
r = i;//r = i-1+1;
}
}
return 0;
}