文章目录
- 说明
- 例题
-
- 例8-1 UVA 120 煎饼
- 例8-2 UVA 1605 联合国大楼
- 例8-3 UVA 1152 和为 0 的 4 个值
- 例8-4 UVA 11134 传说中的车
- 例8-5 UVA 11054 Gergovia 的酒交易
- 例8-6 UVA 1606 两亲性分子(未尝试)
- 例8-7 UVA 11572 唯一的雪花
- 例8-8 UVA 1471 防线(未尝试)
- 例8-9 UVA 1451 平均值(未尝试)
- 例8-10 UVA 714 抄书
- 例8-11 UVA 10954 全部相加
- 例8-12 UVA 12627 奇怪的气球膨胀
- 例8-13 UVA 11093 环形跑道
- 例8-14 UVA 1607 与非门
- 例8-15 UVA 12174 乱序播放记录
- 例8-16 UVA 1608 不无聊的序列
- 例8-17 UVA 1609 不公平竞赛(未尝试)
- 例8-18 UVA 1442 洞穴
- 例8-19 UVA 12265 贩卖土地(未尝试)
说明
本文是我对第八章19道例题的练习总结,建议配合紫书——《算法竞赛入门经典(第2版)》阅读本文。
另外为了方便做题,我在VOJ上开了一个contest,欢迎一起在上面做:第八章例题contest
如果想直接看某道题,请点开目录后点开相应的题目!!!
例题
例8-1 UVA 120 煎饼
题意
有一叠煎饼正在锅里。煎饼共有n(n≤30)张,每张都有一个数字,代表它的大小。厨师每次可以选择一个数k,把从锅底开始数第k张上面的煎饼全部翻过来,即原来在上面的煎饼现在到了下面。
设计一种方法使得所有煎饼按照从小到大排序(最上面的煎饼最小)。输入时,各个煎饼按照从上到下的顺序给出。
思路
这道题目要求排序,但是基本操作却是“颠倒一个连续子序列”。不过没有关系,我们还是可以按照选择排序的思想,以从大到小的顺序依次把每个数排到正确的位置。方法是先翻到最上面,然后翻到正确的位置。由于是按照从大到小的顺序处理,当处理第i大的煎饼时,是不会影响到第1, 2, 3,…, i-1大的煎饼的(它们已经正确地翻到了煎饼堆底部的i-1个位置上)。
开始马虎了,将if (j > 1)判断条件不慎写成了if (j < 1)提交后WA了两发,提醒自己一定要细心啊!
代码
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<vector>
using namespace std;
int n;
int a[31];
void flip(int k)
{
for (int i = 1; i <= k/2; i++)
swap(a[i], a[k-i+1]);
}
int main()
{
string s;
//freopen("in", "r", stdin);
while (getline(cin, s)) {
stringstream ss(s);
int x;
n = 0;
while (ss >> x)
a[++n] = x;
int b[31];
memcpy(b, a, sizeof(a));
sort(b+1, b+n+1);
vector<int> res;
for (int i = n; i >= 1; i--) {
if (a[i] != b[i]) {
int j = 1;
for (; a[j] != b[i]; j++);
if (j > 1) {flip(j); res.push_back(n-j+1);}
flip(i); res.push_back(n-i+1);
}
}
cout << s << endl;
for (int i = 0; i < res.size(); i++)
printf("%d ", res[i]);
printf("0\n");
}
return 0;
}
例8-2 UVA 1605 联合国大楼
题意
你的任务是设计一个包含若干层的联合国大楼,其中每层都是一个等大的网格。有若干国家需要在联合国大楼里办公,你需要把每个格子分配给一个国家,使得任意两个不同的国家都有一对相邻的格子(要么是同层中有公共边的格子,要么是相邻层的同一个格子)。你设计的大厦最多不能超过1000000个格子。
输入国家的个数n(n≤50),输出大楼的层数H、每层楼的行数W和列数L,然后是每层楼的平面图。不同国家用不同的大小写字母表示。
思路
本题的限制非常少,层数、行数和列数都可以任选。正因为如此,本题的解法非常多。我采用的是书中给出的解法:一共只有两层,每层都是n*n的,第一层第i行全是国家i,第二层第j列全是国家j。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
char ans[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int main()
{
int n;
while (scanf("%d", &n) != EOF){
printf("2 %d %d\n", n, n);
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++)
printf("%c", ans[i]);
printf("\n");
}
printf("\n");
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++)
printf("%c", ans[j]);
printf("\n");
}
}
return 0;
}
例8-3 UVA 1152 和为 0 的 4 个值
题意
给定4个n(1≤n≤4000)元素集合A, B, C, D,要求分别从中选取一个元素a, b, c, d,使得
a+b+c+d=0。问:有多少种选法?
思路
中途相遇法。这是一种特殊的算法,大体思路是从两个不同的方向来解决问题,最终“汇集”到一起。
最容易想到的算法就是写一个四重循环枚举a, b, c, d,看看加起来是否等于0,时间复杂度为O(n4),超时。一个稍好的方法是枚举a, b, c,则只需要在集合D里找找是否有元素-a-bc,如果存在,则方案加1。如果排序后使用二分查找,时间复杂度为(n3logn)。
把刚才的方法加以推广,就可以得到一个更快的算法:首先枚举a和b,把所有a+b记录下来放在一个有序数组或者STL的map里,然后枚举c和d,查一查-c-d有多少种方法写成a+b的形式。两个步骤都是O(n2logn),总时间复杂度也是O(n2logn)。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 4000;
int n, m;
int a[4][N];
int x[N*N], y[N*N];
int main(void)
{
int kase;
cin >> kase;
while (kase--) {
cin >> n;
m = n*n;
for (int i = 0; i < n; i ++)
for (int j = 0; j < 4; j ++)
scanf("%d", &a[j][i]);
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
x[i*n+j] = a[0][i] + a[1][j];
for (int i = 0; i < n; i ++)
for (int j = 0; j < n; j ++)
y[i*n+j] = a[2][i] + a[3][j];
sort(y, y+m);
long long ans = 0;
for (int i = 0; i < m; i ++)
ans += (upper_bound(y, y+m, -x[i]) - lower_bound(y, y+m, -x[i]));
printf("%lld\n", ans);
if (kase) printf("\n");
}
return 0;
}
例8-4 UVA 11134 传说中的车
题意
你的任务是在n*n的棋盘上放n(n≤5000)个车,使得任意两个车不相互攻击,且第i个车在一个给定的矩形Ri之内。用4个整数xli, yli, xri, yri(1≤xli≤xri≤n,1≤yli≤yri≤n)描述第i个矩形,其中(xli,yli)是左上角坐标,(xri,yri)是右下角坐标,则第i个车的位置(x,y)必须满足xli≤x≤xri,yli≤y≤yri。如果无解,输出IMPOSSIBLE;否则输出n行,依次为第1,2,…,n个车的坐标。
思路
两个车相互攻击的条件是处于同一行或者同一列,因此不相互攻击的条件就是不在同一行,也不在同一列。可以看出:行和列是无关的,