二分答案

ccsu  1493 青蛙过河

Description

有一条宽度为L(1≤L≤ 1,000,000)的河。河中间有N(0≤N≤20000)块石头,青蛙从河西岸经过这N个石块后,顺利跳到了河的东岸。设河中间每个石块距离西岸的距离为Di(其中Di大于0小于L)。注意:Di是距离起始河岸的距离。 
小明闲着没事,想移掉河中间的M(0≤M≤N)个石块,让一些石块之间的距离增大一点,好叫青蛙没那么容易跳到对岸。 
由于移除M个石块的方法有多种,所以在移除石块之前,小明想知道如果他恰好移除河中M个石块后,青蛙跳的最短距离的最大值可以达到是多少?(青蛙跳一段的距离:西岸与河中第一个石块距离、河中相邻石块的距离、最后一个石块与东岸的距离。青蛙每次都是从一个石块跳到与它相邻的下一个石块)。 

Input

多组输入。第一行顺序输入L, N和M。接下来输入N个正整数(不保证有序),表示N个石块距离起始河岸的距离。

Output

对于组输入,输出移除M个石块后,整个跳跃过程中最短距离的最大值。

Sample Input

25 5 2
2
14
11
21
17

Sample Output

4

Hint

在移除石块之前,跳跃过程中的最小间距2(起点距第1个石块)。移除距离起始岸为2和14的石块后,最小间距就为4了。(0 – 11 – 17 – 21 – 25 )。

Source

poj3258


代码:

#include
#include
using namespace std;
const int size = 50002;
int D[size],L,N,M;
int minl,maxl, ans;
bool Ok(int x)
{
     
      int tot = 0,i = 0, t = 0;
      while (i<=N)
      {
            tot = D[i++];
            while (i <= N && tot < x)
            {//不能小于x, tot小于枚举的x,必须合并
                  tot += D[i++];
                  t++;//拿掉一块石头,合并一次
            }
            if (t > M) return 0;//要使用t次才能满足答案为x的条件,超过了M,不符合等于M的条件,返回0
      }
      //加上,这一句
      //此时tot是最后几块合并的值,如果他小于枚举的答案x
      //且t==M(也就是你之前把可以拿去M块石头的机会都用光了),
      //这种情况tot为最小值,所以x不符合条件
      if (tot < x && M == t) return 0;

      return 1;
}
void Binary()
{
      int l = minl, r = maxl, m;
      while (l <= r)
      {
            m = (l+r)/2;//  
            if (Ok(m)) l = m + 1, ans = m;/*继续枚举更大的答案(最小值最大,
            能够越大尽量越大),并且记录当前可行的答案 */            
            else r = m - 1;//枚举更小的答案
      }
}
int main()
{    
     int i, j, k;
     //freopen("B.in","r",stdin);
     //freopen("tmp.out","w",stdout);
     while (scanf("%d%d%d",&L,&N,&M)!=EOF)
     {
           for (i = 0; i < N; i++)
               scanf("%d",&D[i]);
           sort(D,D+N);//排序,计算石头之间的间距
           D[N] = L;//最后块(也就是对岸)距离起点的距离为L
           minl = maxl = ans = D[0];
           for (i = N; i > 0; i--)
           {
               D[i] = D[i]-D[i-1];//求石头间距
               //printf("%d ",D[i]);
               //if (D[i] == 0) printf("AAA\n");
               if (D[i]

ccsu 1487 数列归并

Description

给定长度为N的序列A,其中1≤N≤100000,1≤A[i] ≤100000。现在要将A分成M段(1≤M≤N),每段有A中的1个或相邻的多个元素构成。例如A={1,3,4,6,7,8}分成3段的一种情况为B={1,(3,4),(6,7,8)}。 
由于将A分成M段的情况有多种,现在要求最大子段和最小的情况。例如上述中B的子段和分别为{1,7,21}. 

Input

每组输入的第一行为N和M,然后N行是序列A,为N个正整数。

Output

输出一个整数,占一行。求最小的情况下的最大子段和。

Sample Input

7 5
100
400
300
100
500
101
400

Sample Output

500

Hint

Sample中的分法为:100+400, 300+100, 500, 101, 400. 其中最大子段和为500,且此种情况为多种情况下最小的。 


#include
#include
#define size 100010
using namespace std;
int n,m;
int a[size];
int ok(int x)
{
    int sum = 0 , t = 0 , i = 0 , flag = 0 ,len;
    while (i < n)
    {
        sum = a[i++];
        int j=i;
        while (j < n && sum < x )
        {
              sum+=a[j++];
        }
        t++;
        if(sum == x) {flag =1; len = j - i + 1; }
        else if( sum > x )  {j --; sum-=a[j];} 
        if( t > m ) return -1;
        i=j;
    }
    if(flag == 1 && n - len >= m -1 ) return  1;
    if(flag == 1 && t <= m)  return 1;
    return 0;
}
int find_ans(int low , int heigh)
{
    int ans = 0 , mid;
    while ( low <= heigh)
    {
        mid = (low + heigh)/2;
        int temp = ok(mid);
        if(temp == -1){ low = mid +1 ;}
        else if(temp )   {heigh = mid - 1; ans = mid;}
        else  heigh = mid -1 ;
    }
    return ans;
}
int main()
{
    int min,max;
    while (scanf("%d%d",&n,&m)!=EOF)
    {
          min = max = 0;
          for(int i=0;i  min)  min = a[i];
                  max+=a[i];
          }
         
          printf("%d\n",find_ans(min,max));
    }
    return 0;
}


你可能感兴趣的:(搜索,排序)