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

文章目录

      • 二分搜索
        • 最大化最值
          • River Hopscotch(POJ 3258)
          • Monthly Expense(POJ 3273)
          • Drying(POJ 3104)
          • Cow Acrobats(POj 3045)
        • 最大化平均值
          • Dropping tests(POJ 2976)
          • K Best(POJ 3111)
        • 查找第k大的值
          • Median(POJ 3579)
          • Matrix(POJ 3685)
        • 最小化第k大的值
          • Telephone Lines(POJ 3662)
          • Garland(POJ 1759)
          • Showstopper(POJ 3484)
        • 尺取法
          • Bound Found(POJ 2556)

二分搜索

最大化最值

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 
#include 
#include 
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=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

代码:

#include 
#include 
#include 
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; ix) 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; ilb+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 
#include 
#include 
#include 
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; ilb+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 
#include 
#include 
#include 
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

最大化平均值

Dropping tests(POJ 2976)

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

  • 题目大意:
    挑战程序设计竞赛(第三章习题总结)_第1张图片
  • 思路:0-1分数划分问题。可以使用二分搜索解决,但是重点在于二分条件。具体参考参考博文。

代码:

#include 
#include 
#include 
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=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
K Best(POJ 3111)

题目链接:K Best

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

代码:

#include 
#include 
#include 
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 idA.x;
    }
}t[MAX];
int k, n;
double lb, ub, mid;//注意数据类型

bool solve(double x)
{
    for(int i=0; i=0) return 1;
    else       return 0;
}
int main()
{
    scanf("%d%d", &n, &k);
    for(int i=0; ilb+1e-6)//注意精度
    {
        mid = (ub+lb)/2;
        if(solve(mid)) lb = mid;
        else           ub = mid;
    }
    for(int i=0; i

查找第k大的值

Median(POJ 3579)

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

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

代码:

#include 
#include 
#include 
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=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; ilb+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 
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
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 G[MAX];

int dijstra(int x)
{
    memset(vis, 0, sizeof(vis));//注意,使用vis可以减枝,否则超时
    fill(d, d+N+1, INF);
    priority_queue 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]=x) dis = d[uv]+1;//一定是>=
            else    dis = d[uv];
            if(!vis[t] && dislb+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 
#include 
#include 
#include 
#include 
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 
#include 
#include 
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=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

尺取法

Bound Found(POJ 2556)

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

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

代码:

#include 
#include 
#include 
#include 
#define LL long long
#define P pair

using namespace std;

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

bool cmp(P a,P b)
{
    return a.firstansr) swap(ansl,ansr);
            printf("%d %d %d\n",ans,ansl+1,ansr);
        }
    }
    return 0;
}

你可能感兴趣的:(挑战程序设计竞赛,挑战程序设计竞赛——经验篇)