文章目录
- 说明
- 习题
-
- 习8-1 UVA 1149 装箱
- 习8-2 UVA 1610 聚会游戏
- 习8-3 UVA 12545 比特变换器
- 习8-4 UVA 11491 奖品的价值
- 习8-5 UVA 177 折痕(未尝试)
- 习8-6 UVA 1611 起重机
- 习8-7 UVA 11925 生成排列
- 习8-8 UVA 1612 猜名次
- 习8-9 UVA 1613 K度图的着色
- 习8-10 UVA 1614 奇怪的股市
- 习8-11 UVA 1615 高速公路
- 习8-12 UVA 1153 顾客是上帝(未尝试)
- 习8-13 UVA 10570 外星人聚会
- 习8-14 UVA 1616 商队抢劫者
- 习8-15 UVA 1617 笔记本(未尝试)
- 习8-16 UVA 1618 弱键(未尝试)
- 习8-17 UVA 11536 最短子序列
- 习8-18 UVA 1619 感觉不错
- 习8-19 UVA 1312 球场(未尝试)
- 习8-20 UVA 1620 懒惰的苏珊
- 习8-21 UVA 1621 跳来跳去
- 习8-22 UVA 1622 机器人
- 习8-23 UVA 1623 神龙喝水
- 习8-24 UVA 10366 龙头滴水(未尝试)
- 习8-25 UVA 11175 有向图D到E(未尝试)
- 习8-26 UVA 12559 找黑圈(未尝试)
- 习8-27 UVA 1580 海盗的宝箱(未尝试)
- 习8-28 UVA 1624 打结(未尝试)
说明
本文是我对第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,再交换两个字符)。
思路
贪心法求解。
由于只需要对不同的位置进行处理,先对两个字符串的不同位置进行分类统计,一共四种情况:
- S中为1,T中为0
- S中为0,T中为1
- S中为?,T中为0
- 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