poj 2010/2110 二分应用(最少补助最大得分/二分+dfs)

题意:奶牛学校招生,n头奶牛报名,要选m头(m为奇数),学校是义务制,所以每头奶牛的学费都由学校负责。每头奶牛都有自己的考试分数和它需要花的学费,学校总共有sum的资金,问合法招生方案中中位数(即排名第(m+1)/2)最高的是多少。

思路:(http://www.cnblogs.com/Thispoet/archive/2011/11/28/2266853.html)二分答案,但是这里的判断不同于一般的二分。因为直接二分不符合单调性。例如下例:

3 5 60

5 100

20 100

30 20

35 20

50 20

此例中,第一次二分的中位数为30,显然无法构成合法的解。但是比他更优的值35却能够构成合法的解。所以要改变二分的策略。

分别将cow按照v(分数)和w(需要的学费)排序。

我的代码里test函数的返回值含义如下:

-1 直接输出,不可能满足条件了

0 这种方案是满足条件的,可以把中位数调大试试

1 这种方案是不满足的,但是把中位数调大就有可能满足了(否则不可能满足)

2 这种方案是不满足的,但是把中位数调小就有可能满足了(否则不可能满足)

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3ffffffffffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 100005
struct node{
    int v,w,id;
}s[N];
int cmp1(node a,node b){
    return a.v < b.v;
}
int cmp2(node a,node b){
    return a.w < b.w;
}
int n,m,sum,tmp[N];
int test(int x){
    int num,left,right;
    for(int i = 0;i<n;i++)
        if(s[i].id == x){
            num = s[i].w;
            break;
        }
    left = right = 0;
    for(int i = 0;i<n;i++){
        if(s[i].id<x && num+s[i].w<=sum && left<m/2){
            left++;
            num += s[i].w;
        }else if(s[i].id>x && num+s[i].w<=sum && right<m/2){
            right++;
            num += s[i].w;
        }
    }
    if(left < m/2 && right<m/2)
        return -1;
    else if(left < m/2)
        return 1;
    else if(right < m/2)
        return 2;
    return 0;
}
int main(){
    int i,j,res=-1,low,high,mid;
    scanf("%d %d %d",&m,&n,&sum);
    for(i = 0;i<n;i++)
        scanf("%d %d",&s[i].v,&s[i].w);
    sort(s,s+n,cmp1);
    for(i = 0;i<n;i++){
        s[i].id = i;
        tmp[i] = s[i].v;
    }
    sort(s,s+n,cmp2);
    low = m/2;
    high = n-m/2-1;
    while(low <= high){
        mid = (low+high)>>1;
        j = test(mid);
        if(j==-1){
            res = -1;
            break;
        }
        if(j==0){
            res = tmp[mid];
            low = mid+1;
        }else if(j==1){
            low = mid+1;
        }else{
            high = mid-1;
        }
    }
    if(res==-1)
        printf("-1\n");
    else
        printf("%d\n",res);
    return 0;
}

2110,给定一个n*n数组,每个位置一个[1,110]之间的数字,求从左上角到右下角的一条路(只能往四邻域行进),使得路径中最大数和最小数的差最小。

思路:二分答案,对每个二分的值mid,还要根据起点的数值进行枚举。然后深搜看看有没有从起点到终点的路径即可。

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <queue>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s))
#define N 105
int n;
int g[N][N],flag[N][N];
int ori[4][2] = {{-1,0},{0,1},{0,-1},{1,0}};
int check(int x,int y,int f,int d){
    return x>0&&y>0&&x<=n&&y<=n&&g[x][y]>=f&&g[x][y]<=f+d&&!flag[x][y];
}
int dfs(int x,int y,int f,int d){
    int i,j;
    if(x==n&&y==n)
        return 1;
    for(i = 0;i<4;i++){
        int xx = x+ori[i][0];
        int yy = y+ori[i][1];
        if(check(xx, yy, f, d)){
            flag[xx][yy] = 1;
            if (dfs(xx, yy, f, d))
                return 1;
        }
    }
    return 0;
}
int main(){
    int i,j,low,high,mid;
    scanf("%d",&n);
    for(i = 1;i<=n;i++)
        for(j = 1;j<=n;j++)
            scanf("%d",&g[i][j]);
    low = 0;
    high = 110;
    while(low <= high){
        mid = (low+high)>>1;
        for(i = g[1][1]-mid;i<=g[1][1];i++){//枚举路径上数值的上界
            clr(flag, 0);
            flag[1][1] = 1;
            if(dfs(1,1,i,mid))
                break;
        }
        if(i>g[1][1])
            low = mid+1;
        else
            high = mid-1;
    }
    printf("%d\n",low);
    return 0;
}


你可能感兴趣的:(poj 2010/2110 二分应用(最少补助最大得分/二分+dfs))