算法竞赛入门经典(第二版)-刘汝佳-第八章 高效算法设计 习题(18/28)

本文详细介绍了《算法竞赛入门经典(第二版)》第八章的28道UVA在线判题平台的习题,包括题意、解题思路和部分代码展示。涉及的算法主要包括贪心法、排序、字符串处理和图论等,旨在帮助读者掌握高效算法设计并提升编程能力。
摘要由CSDN通过智能技术生成

说明

本文是我对第8章28道习题的练习总结,建议配合紫书——《算法竞赛入门经典(第2版)》阅读本文。
另外为了方便做题,我在VOJ上开了一个contest,欢迎一起在上面做:第八章习题contest
如果想直接看某道题,请点开目录后点开相应的题目!!!

习题

习8-1 UVA 1149 装箱

题意
给定N(N≤10^5)个物品的重量Li,背包的容量M,同时要求每个背包最多装两个物品。求至少要多少个背包才能装下所有的物品。

思路
先对物品从小到大排序,然后从前往后贪心法搜索求解。初始化i=0, j=n-1,[i, j)区间表示未放入背包的物品,在(i, j)区间中搜索第一个大于M-a[i]的数k,则其左侧第一个数(也可能这个数就是第i个数)应该和第i个数放入同一个背包。[i, j)区间中,k和k后面的数应当放入单独的背包,因为剩下的物品中最轻的加上他们的各自重量都会超过容量M。

需要细心考虑多种情况。这里再提供两个测试用例:
INPUT

2
4 10
1 4 8 10
5 10
1 4 4 9 10

OUTPUT

3

3

代码

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

const int N = 100001;

int n, l;
int a[N];

int main(void)
{
    int kase;
    scanf("%d", &kase);
    for (int t = 1; t <= kase; t++) {
        scanf("%d%d", &n, &l);
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        sort(a, a+n);

        int res = 0;
        int i = 0, j = n;
        while (i < j) {
            int k = upper_bound(a+i+1, a+j, l-a[i]) - a;
            if (k > i+1) k--;
            res += (j > k) ? j-k : 1;
            //printf("i=%d, j=%d, k=%d, res=%d\n", i, j, k, res);
            i++;
            j = k;
        }
        if (t > 1) printf("\n");
        printf("%d\n", res);
    }

    return 0;
}

习8-2 UVA 1610 聚会游戏

题意
输入一个n(2≤n≤1000,n是偶数)个字符串的集合D,找一个长度最短的字符串(不一定在D中出现)S,使得D中恰好一半串小于等于S,另一半串大于S。如果有多解,输出字典序最小的解。例如,对于{JOSEPHINE, JERRY},输出JF;对于{FRED, FREDDIE},输出FRED。提示:本题看似简单,实际上暗藏陷阱,需要考虑细致、周全。

思路
思路很简单,对字符串排序后找到最中间的两个字符串a和b,然后找到大于等于a且小于b的最短字符串中的字典序最小解。
但果然藏了非常多的陷阱,我一共WA了3发,最后一发还是粗心了,看了半天没看出来,参考了别人的博客才找到的错误。
这里提供一组测试数据吧,基本上能过这组数据的这个题应该就能AC了。
INPUT

2
F
EG
2
FH
EG
2
F
EZ
2
F
EZZE
2
F
EZZEFF
0

OUTPUT

EG
F
EZ
EZZE
EZZF

另外本题还有另一种思路,能够免除if else分析的麻烦。这样写的代码我估计一遍能够AC。
由于不能马上就直接得到答案,就一个一个字母去尝试。这样子就有点类似dfs了,假设a和b在第k位开始不同,先判断在这里填一个字母能否得出答案。这里需注意:不能填了一个字母后就立马搜索下一个情况,因为题目首先要求是最短,所以要在不填下一个字母的情况下,把这个位置可能的字母都填上试试。发现必须要再填下一个字母时,才开始填写下一个字母。
代码可参考这篇博文

代码

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

const int N = 1001;

int n;
string s[N];

int main(void)
{
    while (scanf("%d", &n) && n) {
        for (int i = 0; i < n; i++)
            cin >> s[i];
        sort(s, s+n);

        string a = s[n/2-1], b = s[n/2];
        int k;
        for (k = 0; k < min(a.size(), b.size()); k++)
            if (a[k] != b[k]) break;
        string res = a.substr(0, k);
        if (k < a.size()) {
            if (k+1 == a.size()) res += a[k];
            else if (k+1 == b.size() && b[k] - a[k] == 1) {
                res += a[k];
                for (int i = k+1; i < a.size(); i++) {
                    if(i == a.size()-1) {
                        res += a[i]; break;
                    } else if (a[i] != 'Z') {
                        res += (a[i]+1); break;
                    }
                    res += 'Z';
                }
            } else res += (char)(a[k]+1);
        }
        cout << res << endl;
    }

    return 0;
}

习8-3 UVA 12545 比特变换器

题意
输入两个等长(长度不超过100)的串S和T,其中S包含字符0, 1, ?,但T只包含0和1。你的任务是用尽量少的步数把S变成T。每步有3种操作:把S中的0变成1;把S中的“?”变成0或者1;交换S中任意两个字符。例如,01??00经过3步可以变成001010(方法是先把两个问号变成1和0,再交换两个字符)。

思路
贪心法求解。
由于只需要对不同的位置进行处理,先对两个字符串的不同位置进行分类统计,一共四种情况:

  1. S中为1,T中为0
  2. S中为0,T中为1
  3. S中为?,T中为0
  4. S中为?,T中为1

情况1不能由变换直接得到,需要与其他情况的位置交换得到,只能与情况2或4交换。与情况2交换只耗费1个操作,与情况4交换耗费2个操作(先将情况4的位置的?换成0,再交换)。如果情况1与其他情况交换后还剩余,则无解。情况1处理完后剩下的其他情况均只耗费1个操作。

代码

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

char s[101], t[101];

int main(void)
{
    int n;
    scanf("%d", &n);
    for (int k = 1; k <= n; k++) {
        scanf("%s%s", s, t);

        int type[4];
        memset(type, 0, sizeof(type));
        for (int i = 0; s[i]; i++) {
            if (s[i] != t[i]) {
                if (s[i] == '1') type[0]++;
                else if (s[i] == '0') type[1]++;
                else if (t[i] == '0') type[2]++;
                else type[3]++;
            }
        }

        int res = min(type[0], type[1]);
        type[0] -= res; type[1] -= res;
        if (type[0]) {
            int add = min(type[0], type[3]);
            type[0] -= add; type[3] -= add;
            if (type[0]) res = -1;
            else res += 2*add + type[3] + type[2];
        } else {
            res += type[1] + type[2] + type[3];
        }
        printf("Case %d: %d\n", k, res);
    }

    return 0;
}

习8-4 UVA 11491 奖品的价值

题意
你是一个电视节目的获奖嘉宾。主持人在黑板上写出一个n位整数(不以0开头),邀请你删除其中的d个数字,剩下的整数便是你所得到的奖品的价值。当然,你希望这个奖品价值尽量大。1≤d<n≤10^5。

思路
一开始真的没想到这竟然是一道贪心题目。看了别人的博客才恍然大悟。
我采取的做法是自前向后扫一遍,用vector存储选中的数,当前扫描的数s[i]大于vector尾部的数,那么从它开始将它及其它之前的比s[i]小的数全部删除。同时注意vector中数的个数加上剩下待扫描的数不能低于最终可选数n-d, 防止删除多了。

代码

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

const int N = 100001;

int n, d;
char s[N];

int main(void)
{
    while (scanf("%d%d", &n, &d), n || d) {
        scanf("%s", s);

        vector<char> res;
        for (int i = 0; s[i]; i++) {
            int len;
            while (len = res.size()) {
                if (res[len-1] < s[i] && len+n-i > n-d)
                    res.resize(len-1);
                else
                    break;
            }
            res.push_back(s[i]);
        }
        for (int i = 0; i < n-d; i++)
            printf("%c", res[i]);
        printf("\n");
    }

    return 0;
}

习8-5 UVA 177 折痕(未尝试)

题意

思路

代码



习8-6 UVA 1611 起重机

题意
输入一个1~n(1≤n≤10000)的排列,用不超过96次操作把它变成升序。每次操作都可以选一个长度为偶数的连续区间,交换前一半和后一半。例如,输入5, 4, 6, 3, 2, 1,可以执行1, 2先变成4, 5, 6, 3, 2, 1,然后执行4, 5变成4, 5, 6, 2, 3, 1,然后执行5, 6变成4, 5, 6, 2, 1, 3,然后执行4, 5变成4, 5, 6, 1, 2, 3,最后执行操作1,6即可。
提示:2n次操作就足够了。

思路
顺序将1-n移到自己的位置上即可。将i移动到位置i时,先搜索i目前所在位置j,如果j超出了i与n的中间位置,说明一次操作不能到位,需要两次操作。所以总共最多需要2n次操作。

代码

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

const int N = 10001;

int n;
int a[N];

typedef pair<int, int> P;
vector<P> res;

void exchange(int l, int r)
{
    res.push_back(P(l, r));
    int half = (r-l+1)/2;
    for (int i = l; i < l+half; i++)
        swap(a[i], a[i+half]);
    /*
    printf("=====exchange %d %d\n", l, r);
    for (int i = 1; i <= n; i++)
        printf("%d ", a[i]);
    prin
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值