挑战程序设计竞赛(第三章习题总结)

二分搜索

最大化最值
River Hopscotch(POJ 3258)

题目链接:River Hopscotch

  • 题目大意:一条河长度为 L,河的起点(Start)和终点(End)分别有2块石头,S到E的距离就是L。
    河中有n块石头,每块石头到S都有唯一的距离。
    问现在要移除m块石头(S和E除外),每次移除的是与当前最短距离相关联的石头,要求移除m块石头后,使得那时的最短距离尽可能大,输出那个最短距离。
  • 思路:二分搜索题目最重要的是要找到判断二分的条件。本题目的二分条件不是很好找,但是我们可以转换一下思路。我们可以找能够满足某个最短距离x的区间个数,因为要移走m块石头,所以我们需要找到满足最短距离x的区间个数为n-m-1个。即这些区间的的长度均不小于最短距离x。这需要从头遍历所以石头,然后找出所有大于x的区间(且这些区间不能交叉)。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MAX = 50005;
const LL INF = 1000000005;
LL L, N, M, lb, ub, mid;
LL a[MAX];

bool solve(LL x)
{
    int st = 0, ed = 1, sum = 0;
    for(; ed<N; ed++)
    {
        if(a[ed]-a[st]>=x)//寻找满足条件的区间
        {
            st = ed;//开始下一个区间
            sum++;
        }
    }
    if(sum>=N-M-1) return 1;
    else           return 0;
}
int main()
{
    scanf("%lld%lld%lld", &L, &N, &M);
    a[0] = 0;
    for(int i=1; i<=N; i++)
        scanf("%lld", &a[i]);
    a[N+1] = L;
    N = N+2;//注意加上首尾的石头
    sort(a, a+N);
 
    lb = 0, ub = INF;

    while(ub-lb>1)
    {
        mid = (ub+lb)/2;
        if(solve(mid)) lb = mid;
        else           ub = mid;

    }
    printf("%lld\n", lb);
    return 0;
}
Monthly Expense(POJ 3273)

题目链接:Monthly Expense

  • 题目大意:将N个账款分割成M个连续财务期,使得每个分期账款和的最大值最小。
  • 思路:该二分搜索的二分条件是在确定的每一个分期账款值为x的情况下,计算其能够分m期,且要保证m<M。满足条件时表示x足够大,可以小一点,不满足条件时增加x。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAX = 100001;
int a[MAX];
int N, M, lb, ub, mid;

bool solve(int x)
{
    int sum = 0, cnt = 1;
    for(int i=0; i<N; i++)
    {
        if(a[i]>x) return 1;//证明x太小
        if(sum+a[i]<=x)
        {
            sum += a[i];
        }
        else
        {
            cnt++;
            sum = 0;
            i--;
        }
    }
    if(cnt<=M) return 0;//x足够大
    else       return 1;
}
int main()
{
    lb = 0, ub = 1;
    scanf("%d%d", &N, &M);
    for(int i=0; i<N; i++)
    {
        scanf("%d", &a[i]);
        ub += a[i];
    }
    while(ub>lb+1)
    {
        mid = (ub+lb)/2;
        if(solve(mid)) lb = mid;
        else           ub = mid;
    }
    printf("%d\n", ub);
    return 0;
}
Drying(POJ 3104)

题目链接:Drying

  • 题目大意:Jane希望计算出所有的衣服都烘干的最短时间,每件衣服一开始都有ai的水分,自然状态下每件衣服在单位时间内都会减少一份水,并且jane有烘干机,烘干机每次只能烘干一件衣服,使用机器烘衣服一个单位时间可以让衣服减少K份水(但是烘干时就不会自然蒸发那1份的水分),现在需要让所有衣服的水分含量都降低至0,至少需要多少时间。
  • 思路:二分条件的选择。假设得到了时间x,则需要判断x是否满足条件。则现将所有的ai减去x,表示如果均不用烘干机最后剩的水分。此时可以将ai中小于等于0的排除,因为这些不需要烘干机也能在时间内自动干。然后剩下的就可以放入烘干机中了,只不过此时k变成了k-1。计算使用烘干机的时间cnt,如果cnt<=x,则表明符合条件(x还可以降低),否则不符合条件(x需要增大)。注意:k可能为1,需要单独讨论

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long LL;
const int MAX = 1e5+50;
string s;
LL n, a[MAX], k, lb, ub, mid;
bool solve(LL x)
{
    LL cnt = 0, b;
    for(LL i=0; i<n; i++)
    {
        b = a[i];
        b -= x;
        if(b<=0) continue;
        cnt += b/(k-1);//有可能k为1,需要在前面将这种情况分出讨论
        if(b%(k-1)) cnt++;
    }
    if(cnt<=x) return 0;
    else       return 1;
}
int main()
{
    scanf("%lld", &n);
    lb = 0, ub = 1;
    LL M = 0;
    for(LL i=0; i<n; i++)
    {
        scanf("%lld", &a[i]);
        ub += a[i];
        if(M<a[i]) M = a[i];//选出最大的一个
    }
    scanf("%lld", &k);
    if(k==1)
    {
        printf("%lld\n", M);
        return 0;
    }

    while(ub>lb+1)
    {
        mid = (ub+lb)/2;
        if(solve(mid)) lb = mid;
        else           ub = mid;
    }
    printf("%lld\n", ub);

    return 0;
}
Cow Acrobats(POj 3045)

题目链接:Cow Acrobats
参考博文:POJ Cow Acrobats

  • 题目大意:将N头牛叠成犇,每头牛的力气是S_i,体重是W_i,倒下的风险是身上的牛的体重和减去S_i,求最稳定犇的最大risk。
  • 思路:贪心算法。力气越大,体重越重的在下面,按这样排序就可以得到最优的情况,然后遍历计算这种情况下的最大风险,输出即可。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MAX = 50005;
const LL INF = 1e9+5;
struct Node
{
    LL w, s;
    bool operator < (const Node &A) const
    {
        return w+s<A.w+A.s;
    }
};
Node a[MAX];
int N;
void solve()
{
    LL ans = -INF, sum = 0;
    for(int i=0; i<N; i++)
    {
        ans = max(ans, sum-a[i].s);
        sum += a[i].w;
    }
    printf("%lld\n", ans);
}
int main()
{
    scanf("%d", &N);
    for(int i=0; i<N; i++)
    {
        scanf("%lld%lld", &a[i].w, &a[i].s);
    }
    sort(a, a+N);
    solve();
    return 0;
}
最大化平均值
Dropping tests(POJ 2976)

题目链接:Dropping tests
参考博文:Dropping tests

  • 题目大意:
    在这里插入图片描述
  • 思路:0-1分数划分问题。可以使用二分搜索解决,但是重点在于二分条件。具体参考参考博文。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAX = 1005;
struct Node
{
    double a, b;
};
Node c[MAX];
double t[MAX];
int k, n;
double lb, ub, mid;

bool solve(double x)
{
    for(int i=0; i<n; i++)
    {
        t[i] = 100*c[i].a-x*c[i].b;
    }
    sort(t, t+n);
    double sum = 0;
    for(int i=k; i<n; i++)
        sum += t[i];
    if(sum>=0) return 1;//x可以更大
    else       return 0;
}

int main()
{
    while(scanf("%d%d", &n, &k)!=EOF)
    {
        if(n==0 && k==0) break;
        lb = 0, ub = 101;
        for(int i=0; i<n; i++)
        {
            scanf("%lf", &c[i].a);
        }
        for(int i=0; i<n; i++)
        {
            scanf("%lf", &c[i].b);
        }
        for(int i=0; i<100; i++)
        {
            mid = (ub+lb)/2;
            if(solve(mid)) lb = mid;
            else           ub = mid;
        }
        printf("%.0f\n", lb);
    }
    return 0;
}
K Best(POJ 3111)

题目链接:K Best

  • 题目大意:有N颗珠宝,每颗珠宝的价值为vi,重量为wi。 女主不得已要卖掉部分珠宝,她想留下k颗珠宝,并要求(v1+v2+…vk) / (w1+w2+…wk)的值最大,输出女主留下的珠宝的编号。(可不按输入的顺序输出)。
  • 思路:思路与Dropping tests相似,均是分数划分。只不过该了一下二分条件,并保存了下标用于输出。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAX = 100005;
const int INF = 1e7+1;
struct Node
{
    int v, w;
}a[MAX];
struct Nod
{
    double x;//注意数据类型
    int id;
    bool operator < (const Nod &A) const
    {
        if(x==A.x)
            return id<A.id;
        else
            return x>A.x;
    }
}t[MAX];
int k, n;
double lb, ub, mid;//注意数据类型

bool solve(double x)
{
    for(int i=0; i<n; i++)
    {
        t[i].x = a[i].v-x*a[i].w;
        t[i].id = i+1;
    }
    sort(t, t+n);
    double sum = 0;
    for(int i=0; i<k; i++)
        sum += t[i].x;
    if(sum>=0) return 1;
    else       return 0;
}
int main()
{
    scanf("%d%d", &n, &k);
    for(int i=0; i<n; i++)
        scanf("%d%d", &a[i].v, &a[i].w);
    lb = 0, ub = INF;
    while(ub>lb+1e-6)//注意精度
    {
        mid = (ub+lb)/2;
        if(solve(mid)) lb = mid;
        else           ub = mid;
    }
    for(int i=0; i<k; i++)
    {
        if(i!=0) printf(" ");
        printf("%d", t[i].id);
    }
    printf("\n");
    return 0;
}
查找第k大的值
Median(POJ 3579)

题目链接:Median
参考博文:POJ 3579 Median 查找中间值 二分

  • 题目大意:给出n(3<=n<=100000)个数,f(i,j)=|a[i]-a[j]| (1<=i<j<=n)。求所有的f(i,j)里面中位数的值。
  • 思路:给出中位数mid,需要判断该中位数是否满足条件。排序所用元素,遍历每一个元素,然后二分查找得到大于该元素ai并小于ai+mid的元素个数,然后累加得到在mid左边的元素的个数cnt,并判断cnt与m/2(m是差值个数)的关系。大于等于表示mid太大,小于表示mid太小。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

typedef long long LL;
const int MAX = 100005;
LL x[MAX], N, M;
LL lb, ub, mid;

bool solve(LL mid)
{
    LL cnt = 0;
    for(int i=0; i<N; i++)
    {
        cnt += upper_bound(x+i, x+N, x[i]+mid)-x-i-1;
    }
    if(cnt>=M) return 0;//mid可以减少
    else       return 1;//mid可以增加
}
int main()
{
    while(scanf("%lld", &N)!=EOF)
    {
        M = (N-1)*N/2;
        if(M%2)  M = M/2+1;
        else     M = M/2;
        for(int i=0; i<N; i++)
            scanf("%lld", &x[i]);
        sort(x, x+N);

        lb = 0, ub = x[N-1]-x[0]+1;
        while(ub>lb+1)
        {
            mid = (lb+ub)/2;
            if(solve(mid)) lb = mid;
            else           ub = mid;
        }
        printf("%lld\n", ub);
    }
    return 0;
}
Matrix(POJ 3685)

题目链接:Matrix
参考博文:POJ - 3685 Matrix 二分

  • 题目大意:有一个N * N的矩阵,其中Aij = i * i + i * 100000 - 100000 * j + j * j + i * j,问这个矩阵中,第M小的数是多少。
  • 思路:需要从题目中得到当j不变时,随着i的增大,Aij也增大。所以每一列均是已经排好序的数组。所以和Median(POJ 3579)相似,累加每一列小于给定值mid的个数,得到cnt,然后判断cnt与M的关系,小于表示mid太小,否则表示mid太大。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;
const LL INF = 1<<29;
int T;
LL lb, ub, mid, N, M;

LL caculate(LL m, LL n)
{
    return m*m+100000*m+n*n-100000*n+m*n;
}
bool solve(LL x)
{
    LL cnt = 0;
    for(int j=1; j<=N; j++)
    {
        LL l = 1, r = N, mid;
        //寻找合适的元素下标
        while(r>=l)
        {
            mid = (r+l)/2;
            if(caculate(mid, j)<=x)
                l = mid+1;
            else
                r = mid-1;
        }
        cnt += l-1;
    }
    if(cnt>=M) return 0;
    else       return 1;
}

int main()
{
    scanf("%d", &T);
    while(T--)
    {
        scanf("%lld%lld", &N, &M);
        //注意初始数据
        lb = -(LL)(N*N*3+100000*N), ub = (LL)(N*N*3+100000*N);
        while(ub>lb+1)
        {
            mid = (lb+ub)/2;
            if(solve(mid)) lb = mid;
            else           ub = mid;
        }
        printf("%lld\n", ub);
    }
    return 0;
}
最小化第k大的值
Telephone Lines(POJ 3662)

题目链接:Telephone Lines
参考博文:POJ 3662 Telephone Lines 题解 《挑战程序设计竞赛》

  • 题目大意:N个电线杆P条线可选,K条线内免费,否则花费免费额度外最长的那一根。求最小花费。
  • 思路:最短路+二分。因为在k条线内免费,所以可以设免费额度外最长的一根电线长度为mid,则通过最短路遍历得到长度不小于mid的边的个数,如果个数大于K,则表示mid比较小,否则mid足够大,最终输出的是lb。其中d[]表示在最短路中边的长度高于mid的边的个数。具体解释参考博文。

代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAX = 1005;
const int INF = 1000001;
int N, P, K, A, B, L, lb, ub, mid;
int d[MAX], vis[MAX];
struct edge
{
    int v, d;
    edge(int v = 0, int d = 0): v(v), d(d){}
    bool operator < (const edge &A) const
    {
        return d>A.d;
    }
};
vector<edge> G[MAX];

int dijstra(int x)
{
    memset(vis, 0, sizeof(vis));//注意,使用vis可以减枝,否则超时
    fill(d, d+N+1, INF);
    priority_queue<edge> q;
    d[1] = 0;
    q.push(edge(1, d[1]));

    while(!q.empty())
    {
        edge u = q.top(); q.pop();
        int uv = u.v, ud = u.d;
        vis[uv] = 1;
        if(d[uv]<ud) continue;//减枝
        for(int i=0; i<G[uv].size(); i++)
        {
            int t = G[uv][i].v, c = G[uv][i].d, dis;
            if(c>=x) dis = d[uv]+1;//一定是>=
            else    dis = d[uv];
            if(!vis[t] && dis<d[t])
            {
                d[t] = dis;
                q.push(edge(t, d[t]));
            }
        }
    }
    return d[N];
}
int main()
{
    scanf("%d%d%d", &N, &P, &K);
    for(int i=0; i<P; i++)
    {
        scanf("%d%d%d", &A, &B, &L);
        G[A].push_back(edge(B, L));
        G[B].push_back(edge(A, L));
    }
    lb = 0, ub = INF+2;
    while(ub>lb+1)
    {
        mid = (ub+lb)/2;
        int n = dijstra(mid);
        if(n>K)            lb = mid;//一定保证大于等于mid的至少有k+1个,才能代表mid可以作为结果
        else               ub = mid;
    }
    if(lb>INF)
        printf("-1\n");
    else
        printf("%d\n", lb);
    return 0;
}
Garland(POJ 1759)

题目链接:Garland
参考博文:POJ—1759(Garland,二分一个,求另一个的最优)

  • 题目大意:有n个数字H,H[i]=(H[i-1]+H[i+1])/2-1,已知H[1],求最大H[n],
    使得所有的H均大于0.
  • 思路:我们得到递推式子H[i]=2*H[i-1]+2-H[i-2],发现H[n]和H[2]成正相关。
    所以我们只要二分H[2]的取值,同时计算每个H是否大于等于0即可。同时可以减枝优化一下。

代码:

#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;

const double INF = 1002;
double A, B, H, lb , ub, mid;
int N;

bool solve(double x)
{
    double H2 = A, H1 = x;//一定使用变量存储
    for(int i=3; i<=N; i++)
    {
        H = H1*2+2-H2;
        if(H<0) return 1;//小于0,表示x不够大
        if(H+2>H1) return 0;//减枝,递增且已经大于0,则不会再小于0
        H2 = H1;
        H1 = H;
    }
    return 0;
}
int main()
{
    while(scanf("%d%lf", &N, &A)!=EOF)
    {
        lb = -1, ub = INF;
        //二分求第2个变量
        for(int i=0; i<100; i++)
        {
            mid = (lb+ub)/2.0;
            if(solve(mid)) lb = mid;
            else           ub = mid;
        }
        //得到最优的第2个变量,再从头到尾算一遍
        for(int i=3; i<=N; i++)
        {
            H = ub*2+2-A;
            A = ub;
            ub = H;
        }
        printf("%.2f\n", H);
    }
    return 0;
}
Showstopper(POJ 3484)

题目链接:Showstopper
参考博文:POJ-3484-Showstopper

  • 题目大意:给出一组x,y,z 每组包含一个集合{x+k*z<=y,k=0,1,2,3…}, 所有集合中只有一个数出现奇数次或者全部出现偶数次,如果有数出现奇数次,输出那个数以及出现的次数,否则输出 no corruption。
  • 思路:因为只可能有一个数出现奇数次,假设为x,所有数出现次数的和依次为 偶 偶 偶 奇(x) 奇 奇 奇 奇 x之前为偶数,x之后为奇数,而所有数出现的次数可以根据公式直接算出,再根据这个单调性二分出答案。本题目的重点在与输入,不同样例之间有多个空行,且不知道有多少个样例,每个样例有多少行数据。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;
const long long INF = 1LL<<35;
const int MAX = 5000000;
LL X, Y, Z, t, ans;
LL lb, ub , mid;
string s;
struct Node
{
    LL x, y, z;
}f[MAX];

LL solve(LL x)
{
    LL cnt = 0;
    for(int i=0; i<t; i++)
    {
        if(x>=f[i].x)
            cnt += min(x-f[i].x, f[i].y-f[i].x)/f[i].z+1;
    }
    return cnt;
}

void compute()
{
    lb = 0, ub = INF, ans = 0;
    while(ub>lb+1)
    {
        mid = (lb+ub)/2;
        if(solve(mid)%2==0) lb = mid;
        else                ub = mid;
    }
    if(ub==INF)
    {
        printf("no corruption\n");
    }
    else
    {
        printf("%lld %lld\n", ub, solve(ub)-solve(ub-1));
    }
}

int main()
{
    //本题的难点在于输入。不同测试样例之间有多个空行,输入结束后需要在循环外在加一个处理
    t = 0;
    while(getline(cin, s))
    {
        if(s.size()==0)
        {
            if(t==0)
                continue;
            compute();
            t = 0;
        }
        else
        {
            int a[5] = {0}, k = 0;
            for(int i=0; i<s.size(); i++)
            {
                if(s[i]!=' ')
                {
                    a[k] = a[k]*10+s[i]-'0';
                }else
                {
                    k++;
                }
            }
            f[t].x = a[0], f[t].y = a[1], f[t++].z = a[2];
        }
    }
    if(t)
        compute();
    return 0;
}
尺取法
Bound Found(POJ 2556)

题目链接:Bound Found
参考博文:POJ-Bound Found | 尺取法+绝对值特性

  • 题目大意:给定一个数列,求某个子序列的和的绝对值最接近给定的t,输出这个序列的和的绝对值,左右端点。
  • 思路:将和的绝对值转换为前缀和数组,然后将数组排序得到单调数组,然后可以使用尺取法,取的是任意两个元素之差(单调增)。

代码:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define LL long long
#define P pair<int,int>

using namespace std;

const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
P p[maxn];

bool cmp(P a,P b)
{
    return a.first<b.first;
}

int main()
{
    int n,k,x,t,sum;
    while(scanf("%d%d",&n,&k)&&n+k)
    {
        sum=0;
        p[0]=make_pair(0,0);
        for(int i=1; i<=n; ++i)
        {
            scanf("%d",&x);
            sum=sum+x;
            p[i]=make_pair(sum,i);
        }
        sort(p,p+n+1,cmp);

        while(k--)
        {
            scanf("%d",&t);
            sum=0;
            int l=0,r=1,ansl,ansr,mi=INF,ans;
            while(r<=n&&mi)
            {
                sum=p[r].first-p[l].first;
                if(abs(sum-t)<=mi)
                {
                    mi=abs(sum-t);
                    ans=sum;
                    ansl=p[l].second;
                    ansr=p[r].second;
                }
                if(sum<t) r++;
                else l++;
                if(l==r) r++;
            }
            if(ansl>ansr) swap(ansl,ansr);
            printf("%d %d %d\n",ans,ansl+1,ansr);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值