本文是我对第五章12道例题的练习总结,建议配合紫书——《算法竞赛入门经典(第2版)》阅读本文。
另外为了方便做题,我在VOJ上开了一个contest,欢迎一起在上面做:第五章例题contest
如果想直接看某道题,请点开目录后点开相应的题目!!!
思路
这个题比较基础,排序用sort,查找用lower_bound,都是STL的标准函数。注意lower_bound函数的返回值是不小于查找数的第一个位置对应的指针,找不到则返回a+n(也就是函数的第二个参数)。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 10000;
int main(void)
{
int n, q, t = 0;
int a[N], x;
while (scanf("%d%d", &n, &q) != EOF && n) {
for (int i = 0; i < n; i ++)
scanf("%d", &a[i]);
sort(a, a+n);
printf("CASE# %d:\n", ++t);
while (q--) {
scanf("%d", &x);
int m = lower_bound(a, a+n, x) - a;
printf("%d ", x);
if (m == n || a[m] != x) printf("not found\n");
else printf("found at %d\n", m+1);
}
}
return 0;
}
思路
由于此题中木块堆的长度一直在变化,并需要频繁的添加和删除元素操作,用vector是最合适的。
注意学习vector的功能函数用法,如size() resize() push_back()等。
另外,这个题的四种操作中具体操作有一定的重复,合理拆分操作能够使代码模块化更好。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 25;
typedef pair<int, int> P;
int n;
vector<int> pile[N];
P find_pile(int a)
{
for (int i = 0; i < n; i ++) {
for (int j = 0; j < pile[i].size(); j ++) {
if (pile[i][j] == a) return P(i, j);
}
}
return P(n, n);
}
void clear_above(P p)
{
int i = p.first, j = p.second;
for (int k = j+1; k < pile[i].size(); k ++) {
int a = pile[i][k];
pile[a].push_back(a);
}
pile[i].resize(j+1);
}
void pile_over(P p, int b)
{
int i = p.first, j = p.second;
for (int k = j; k < pile[i].size(); k ++) {
int a = pile[i][k];
pile[b].push_back(a);
}
pile[i].resize(j);
}
void print()
{
for (int i = 0; i < n; i ++) {
printf("%d:", i);
for (int j = 0; j < pile[i].size(); j ++) {
printf(" %d", pile[i][j]);
}
printf("\n");
}
}
int main(void)
{
cin >> n;
for (int i = 0; i < n; i ++) pile[i].push_back(i);
string s1, s2;
int a, b;
P p1, p2;
while (cin >> s1 && s1 != "quit") {
cin >> a >> s2 >> b;
p1 = find_pile(a);
p2 = find_pile(b);
if (p1.first == p2.first) continue;
if (s1 == "move") clear_above(p1);
if (s2 == "onto") clear_above(p2);
pile_over(p1, p2.first);
//print();
//printf("===============\n");
}
print();
return 0;
}
思路
此题中涉及的知识点有:
1、set的用法(注意set中没有重复的值)
2、isalpha isdigit等函数的用法
3、迭代器iterator的用法
注意学习。
代码
#include <cstdio>
#include <iostream>
#include <string>
#include <cctype>
#include <set>
using namespace std;
set<string> d;
int main() {
string s;
char ch;
while (getline(cin, s)) {
for (int i = 0; i < s.size(); i++) {
if (!isalpha(s[i]))
continue;
string tmp;
while (i < s.size() && isalpha(s[i]))
tmp += tolower(s[i++]);
d.insert(tmp);
}
}
for (set<string>::iterator i = d.begin(); i != d.end(); i++)
cout << *i << endl;
return 0;
}
思路
我的代码用了两个map,不如书中的例程代码用了一个map和一个vector。
代码
#include <iostream>
#include <algorithm>
#include <string>
#include <map>
#include <set>
using namespace std;
map<string, string> sorted;
map<string, int> num;
int main(void)
{
string s, s1;
char ch[21];
while (cin >> s && s != "#") {
int n = s.size();
for (int i = 0; i < s.size(); i ++)
ch[i] = tolower(s[i]);
ch[n] = '\0';
sort(ch, ch+n);
s1 = ch;
sorted[s] = s1;
if (num.find(s1) == num.end()) num[s1] = 1;
else num[s1] ++;
}
set<string> res;
for (map<string, string>::iterator it = sorted.begin();
it != sorted.end(); it ++) {
if (num[it->second] == 1) res.insert(it->first);
}
for (set<string>::iterator it = res.begin();
it != res.end(); it ++) {
cout << *it << endl;
}
return 0;
}
思路
这个题主要使用的数据结构是stack,但最重要的知识点却不是stack!
本题有一种用映射简化数据结构的思想:将另一种相对复杂的数据结构用map映射成int型id,不仅能够大大降低存储空间,而且也可以大大提高查找效率。
因此这是第五章最重要的一道题,没有之一!!!
后续习题或例题中有好几道用到了这种思想,后面会讲到。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<stack>
using namespace std;
typedef set<int> Set;
map<Set, int> IDs;
vector<Set> Sets;
stack<int> st;
int getID(Set s)
{
if (IDs.count(s)) return IDs[s];
Sets.push_back(s);
return IDs[s] = Sets.size()-1;
}
void init()
{
Set s0;
getID(s0);
}
void push()
{
st.push(0);
}
int main()
{
int t, n;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
init();
char op[10];
Set s1, s2, s3;
int id;
while (n--) {
scanf("%s", op);
switch(op[0]) {
case 'P':
st.push(0);
break;
case 'D':
st.push(st.top());
break;
case 'U':
s1 = Sets[st.top()]; st.pop();
s2 = Sets[st.top()]; st.pop();
for (Set::iterator it = s2.begin(); it != s2.end(); it++)
s1.insert(*it);
st.push(getID(s1));
break;
case 'I':
s1 = Sets[st.top()]; st.pop();
s2 = Sets[st.top()]; st.pop();
s3.clear();
for (Set::iterator it = s2.begin(); it != s2.end(); it++)
if (s1.count(*it)) s3.insert(*it);
st.push(getID(s3));
break;
case 'A':
id = st.top(); st.pop();
s2 = Sets[st.top()]; st.pop();
s2.insert(id);
st.push(getID(s2));
break;
default:
break;
}
printf("%d\n", Sets[st.top()].size());
}
printf("***\n");
}
return 0;
}
思路
队列的特点是先入先出,通常排队系统的题会用到队列。
本题用两个队列解决:每个团体有一个队列(元素为团体成员ID),团队整体又形成一个队列(元素为团体ID)。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
const int T = 1000;
int t;
map<int, int> his_team;
queue<int> qt, q[T];
void init_queue()
{
while (qt.size()) qt.pop();
for (int i = 0; i < t; i++)
while (q[i].size()) q[i].pop();
}
int main(void)
{
int kase = 0, n, m;
char op[10];
while (scanf("%d", &t) && t) {
for (int i = 0; i < t; i++) {
scanf("%d", &n);
for (int j = 0; j < n; j++) {
scanf("%d", &m);
his_team[m] = i;
}
}
init_queue();
printf("Scenario #%d\n", ++kase);
while (scanf("%s", op) && strcmp(op, "STOP")) {
if (op[0] == 'E') {
scanf("%d", &m);
int i = his_team[m];
if (q[i].empty()) qt.push(i);
q[i].push(m);
} else {
int i = qt.front();
printf("%d\n", q[i].front());
q[i].pop();
if (q[i].empty()) qt.pop();
}
}
printf("\n");
}
return 0;
}
思路
这个题的uva上一直submit error,但我程序的运行结果与其他博客对比过,完全正确。
此题没有必要一定用long long,因为所求结果(第1500个丑数)并不超过int表示范围。但我结果*5却会超过,所以我的程序中用了long long。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
int main()
{
LL a = 0, b = 0;
priority_queue<LL, vector<LL>, greater<LL> > pq;
pq.push(1);
int cnt = 0;
while (pq.size()) {
a = pq.top();
pq.pop();
if (a != b) {
cnt++;
if (cnt == 1500) break;
pq.push(a*2);
pq.push(a*3);
pq.push(a*5);
b = a;
}
}
printf("%lld\n", a);
return 0;
}
思路
注意这个题要求的是按列优先方式输出,这与程序正常输出的方向不同。因此需要改变循环层次,具体见程序。
另外只要有字符后面就要补全M或M+2个字符,如果没有字符则不用。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100;
const int M = 60;
void print_char(int k, char c)
{
while (k--) printf("%c", c);
}
int main()
{
int n, m, col, row;
string s[N];
while (~scanf("%d", &n)) {
m = 0;
for (int i = 0; i < n; i++) {
cin >> s[i];
m = max(m, (int)s[i].size());
}
sort(s, s+n);
col = (M-m)/(m+2) + 1;
row = n/col + ((n%col) ? 1 : 0);
print_char(M, '-');
printf("\n");
for (int j = 0; j < row; j ++) {
for (int i = 0; i < col; i ++) {
int k = i*row+j;
if (i == col-1) {
if (k < n) {
cout << s[k];
print_char(m-s[k].size(), ' ');
}
printf("\n");
} else {
cout << s[k];
print_char(m+2-s[k].size(), ' ');
}
}
}
}
return 0;
}
思路
四重循环枚举肯定会超时,实际上存在大量无用的比较。实际上只需要枚举c1和c2,然后从上到下扫描各行。每次碰到一个新的行r,把c1,c2两列的内容作为一个二元组存到一个map中。如果map的键值中已经存在这个二元组,该二元组映射到的就是所要求的r1,而当前行就是r2。
还有一个细节问题:如何表示由c1,c2两列组成的二元组?一种方法是直接用两个字符串拼成一个长字符串,速度更快的则是进行预处理——给所有字符串分配一个编号,则整个数据库中每个单元格都变成了整数,上述二元组就变成了两个整数。这是例子5-5中介绍的技巧。
另外在做的时候有一个小插曲,第一次提交TLE了,查了一下程序发现每组数据计算时忘记重新对map进行初始化。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
const int N = 10000;
const int M = 10;
typedef pair<int, int> P;
map<P, int> firstpos;
map<string, int> str2key;
vector<string> key2str;
int ID(string &s)
{
if (str2key.count(s)) return str2key[s];
key2str.push_back(s);
return str2key[s] = key2str.size()-1;
}
int main()
{
int r, c;
char ch;
string tab[N][M];
while (cin >> r >> c) {
getchar();
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
tab[i][j] = "";
while ((ch = getchar()) != ',' && ch != '\n')
tab[i][j] += ch;
}
}
int flag = true;
for (int j = 0; j < c; j++) {
for (int k = j+1; k < c; k++) {
firstpos.clear();
str2key.clear();
key2str.clear();
for (int i = 0; i < r; i++) {
P p(ID(tab[i][j]), ID(tab[i][k]));
if (firstpos.count(p)) {//find
printf("NO\n");
printf("%d %d\n", firstpos[p]+1, i+1);
printf("%d %d\n", j+1, k+1);
flag = false;
break;
} else
firstpos[p] = i;
}
if (flag == false) break;
}
if (flag == false) break;
}
if (flag) printf("YES\n");
}
return 0;
}
思路
该题尚未尝试,先占个位置。
代码
思路
该题尚未尝试,先占个位置。
代码
思路
该题尚未尝试,先占个位置。
代码