一天一道算法题--5.27--思维题

感谢 微信平台: 一天一道算法题 每天带来很有意义的东西 -----------

今天它提供的题目 可以参照

  戳我

题目大意:

有N个正整数构成的一个序列 给定整数S 求长度最短的连续序列 使他们的和大于或等于S
输入第一行是 n 和 S (10<n<1000000 S<10^9 )
第二行输入这n个数 均不能超过10000

一些解题思路:

1.

暴力  0(n^3) 时间复杂度过高 直接略过

2. 

我们这边运用的技巧是 前缀和思想 这种方法是 O(n^2)   应该可以接受...
如果 你以前没有听过 那么现在有必要 好好记住并理解它了 因为它的用处真的很大
S[i] = S[1] + S[2] + …… + S[I] ;  S[j] - S[i] = S[i] + S[i+1] + …… + S[j] ;

那么 其实代码就不难实现了 只要对于每个 I 都去遍历 它后面的数组小标 他们的差值 在对value进行比较 如果满足大于等于的条件 就记录下来  这个序列长度自然就是 j-i+1

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 
 5 const int size = 1000000+10;
 6 int sum[size];
 7 int main()
 8 {
 9     int t;
10     int i , j;
11     int n , value , num;
12     int minLen;
13     while( ~scanf("%d",&t) )
14     {
15         while( t-- )
16         {
17             scanf( "%d %d",&n,&value );
18             sum[0] = 0;//这是个很关键的预处理
19             minLen = n+1;
20             for( i = 1 ; i<=n ; i++ )
21             {
22                 scanf( "%d",&num );
23                 sum[i] = sum[i-1] + num;
24             }
25             for( i = 1 ; i<=n ; i++ )
26             {
27                 for( j = i ; j<=n ; j++ )
28                 {
29                     if( sum[j]-sum[i-1] >=value && (j-i+1 < minLen) )
30                     {
31                         minLen = j-i+1;
32                     }
33                 }
34             }
35             printf( "%d
", minLen==n+1?0:minLen );
36         }
37     }
38     return 0;
39 }
View Code

一个很悲剧的消息  我们上面的代码 超时了 在zoj的平台上  那么 有必要再进行一下优化了

这边的code4god 给我们提供了二分的思想

你可以这样理解:

我们可以尝试只枚举终点 J , 我们需要找到一个sum[j]-sum[i-1] >= S 并且 i 尽量大 那么序列长度就会变小

注意到这里的sum数组 是递增的 那么就可以用二分查找     STL直接为我们提供了更好的lower_bound() 函数

我以前 也听过它 好像没用过它  从这次来看 真的很强大

至于怎么使用它 你可以自行百度

好了 现在贴下 这个再次优化后的算法

 1 /*
 2 lower_bound(a,b,c)返回的是从a到b-1区间内第一个大于等于c的指针
 3 正确的做法是lower_bound( sum , sum+i , sum[i]-value+1)
 4 必须找sum[i]-value+1的下界
 5 */
 6  
 7 
 8 
 9 #include <iostream>
10 #include <cstdio> 
11 #include <algorithm>
12 using namespace std;
13 
14 const int size = 100000+10;
15 int sum[size];
16 
17 int main()
18 {
19     int t;
20     int n , value , num;
21     int minLen , pos;
22     while( ~scanf("%d",&t) )
23     {
24         while( t-- )
25         {
26             scanf( "%d %d",&n,&value );
27             sum[0] = 0; 
28             minLen = n+1;
29             for( int i = 1 ; i<=n ; i++ )
30             {
31                 scanf( "%d",&num );
32                 sum[i] = sum[i-1] + num;
33                 pos = lower_bound( sum , sum+i , sum[i]-value+1 ) - sum; 
34                 if( i>=1 )
35                 {
36                     printf( "%d %d
",i,pos );
37                 }
38                 if( pos>0 )
39                 {
40                     minLen = min( minLen , i-pos+1 );
41                 }
42             }
43             printf( "%d
",minLen==n+1?0:minLen );
44         }
45     }
46     return 0;
47 }
View Code

貌似 时间一下子 缩短到20ms了吧 没记错的话。。。

难得一次是在 当天写完的...

 这里 我必须补充一下  如果我的Lower_bound的形参写成 sum[i]-value  在zoj和poj我都测试过 是可以AC的  可能是它们的测试数据不够强大的原因 因为下面这组例子 它输出的就是错的

5 3

1 1 1

这边来分析一下原因-》来源于大神

现在 我们想找一个k 使得k<i sum[i] - sum[k-1]>=value
而且 对于所有的p>k sum[i]-sum[p]<value
我们知道 所有满足sum[i]-sum[k-1]>=value 的都同时满足 sum[k-1]<=sum[i]-value
即 sum[k-1]<sum[i]-value+1
所以 我们就可以通过lower_bound(sum , sum+i , sum[i]-value+1)来实现
我这边再强调一下 lower_bound(a,a+n,value)的查询区间是递增的左闭右开区间  对于这个端点是否取到应该是重点

刚刚看到了 一个大牛对stl中 lower_bound  和 upper_bound的理解  我就顺便把它的实现   贴过来了

iterator lower_bound( const key_type &key ): 返回一个迭代器 , 指向键值>= key的第一个元素。
iterator upper_bound( const key_type &key ):返回一个迭代器 , 指向键值> key的第一个元素。 也可以理解成键值>=KEY的最后一个元素

博客链接

根据2分实现的lower_bound

 1 int my_lower_bound(int *array, int size, int key)
 2 {
 3     int first = 0, last = size-1;
 4     int middle, pos=0;       //需要用pos记录第一个大于等于key的元素位置
 5 
 6     while(first < last)
 7     {
 8         middle = (first+last)/2;
 9         if(array[middle] < key){      //若中位数的值小于key的值,我们要在右边子序列中查找,这时候pos可能是右边子序列的第一个
10             first = middle + 1;
11             pos = first;
12         }
13         else{
14             last = middle;           //若中位数的值大于等于key,我们要在左边子序列查找,但有可能middle处就是最终位置,所以我们不移动last,
15             pos = last;              //而是让first不断逼近last。
16         }
17     }
18     return pos;
19 }
View Code

stl中的源码实现lower_bound

 1 //这个算法中,first是最终要返回的位置
 2 int lower_bound(int *array, int size, int key)
 3 {
 4     int first = 0, middle;
 5     int half, len;
 6     len = size;
 7 
 8     while(len > 0) {
 9         half = len >> 1;
10         middle = first + half;
11         if(array[middle] < key) {     
12             first = middle + 1;          
13             len = len-half-1;       //在右边子序列中查找
14         }
15         else
16             len = half;            //在左边子序列(包含middle)中查找
17     }
18     return first;
19 }
View Code

同样 下面就是 它的兄弟 upper_bound

 1 int my_upper_bound(int *array, int size, int key)
 2 {
 3     int first = 0, last = size-1;
 4     int middle, pos = 0;
 5 
 6     while(first < last)
 7     {
 8         middle = (first+last)/2;
 9         if(array[middle] > key){     //当中位数大于key时,last不动,让first不断逼近last
10             last = middle;
11             pos = last;
12         }
13         else{
14             first = middle + 1;     //当中位数小于等于key时,将first递增,并记录新的位置
15             pos = first;
16         }
17     }
18     return pos;
19 }
View Code

stl源码实现upper_bound

 1 int upper_bound(int *array, int size, int key)
 2 {
 3     int first = 0, len = size-1;
 4     int half, middle;
 5 
 6     while(len > 0){
 7         half = len >> 1;
 8         middle = first + half;
 9         if(array[middle] > key)     //中位数大于key,在包含last的左半边序列中查找。
10             len = half;
11         else{
12             first = middle + 1;    //中位数小于等于key,在右半边序列中查找。
13             len = len - half - 1;
14         }
15     }
16     return first;
17 }
View Code

今天 听到一句话  

-----------勇敢去爱 不要逃------------

我想破坏意境的加一句  :

-----------一切 无所谓---------------

just follow your heart
原文地址:https://www.cnblogs.com/radical/p/3755335.html