第五章 C++与STL入门(例题篇:STL部分)--算法竞赛入门经典

第五章 C++与STL入门(例题篇:STL部分)

例题5-1:大理石在哪儿(UVa10474) P108

排序与检索

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10000;
int main()
{
	/*ios::sync_with_stdio(false);*/
	freopen("input.txt", "r", stdin);
	int n, q, x, a[maxn], kase = 0;
	while (scanf("%d%d", &n, &q) == 2 && n)
	{
		printf("CASE# %d:\n", ++kase);
		for (int i = 0;i < n;i++)
		{
			scanf("%d", &a[i]);
		}
		sort(a, a + n);
		while (q--)
		{
			scanf("%d", &x);
			int p = lower_bound(a, a + n, x) - a;
			if (a[p] == x)
			{
				printf("%d found at %d\n", x, p + 1);
			}
			else
			{
				printf("%d not found\n", x);
			}
		}
	}
	return 0;
}

代码中使用了algorithm头文件中的sort和lower_bound;loer_bound的作用是查找“大于或者等于x的第一个位置”。

例题5-2:木块问题(The Blocks Problem,UVa101) P110

不定长数组:vector

#include<iostream>
#include<vector>
#include<string>
using namespace std;
const int maxn = 30;
int n;
vector<int> pile[maxn];

//找到木块a所在的pile和height
void find_block(int a, int &p, int &h)
{
	for (p = 0;p < n;p++)
	{
		for (h = 0;h < pile[p].size();h++)
		{
			if (pile[p][h] == a)
				return;
		}
	}
}

//把第p堆高度为h的木块上方的所有木块移回原位
void clear_above(int p, int h)
{
	for (int i = h + 1;i < pile[p].size();i++)
	{
		int b = pile[p][i];
		pile[b].push_back(b);//把木块b放回原位
	}
	pile[p].resize(h + 1);
}

//把第p堆高度为h及其上方的木块整体移动到p2堆的顶部
void pile_onto(int p, int h, int p2)
{
	for (int i = h;i < pile[p].size();i++)
	{
		pile[p2].push_back(pile[p][i]);
	}
	pile[p].resize(h);
}

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()
{
	int a, b;
	cin >> n;
	string s1, s2;
	for (int i = 0;i < n;i++)
	{
		pile[i].push_back(i);
	}
	while (cin >> s1 >> a >> s2 >> b)
	{
		int pa, pb, ha, hb;
		find_block(a, pa, ha);
		find_block(b, pb, hb);
		if (pa == pb)
			continue;
		if (s2 == "onto")
			clear_above(pb, hb);
		if (s1 == "move")
			clear_above(pa, ha);
		pile_onto(pa, ha, pb);
	}
	print();
	return 0;
}

上述代码一个值得学习的技巧:输入一共有4种指令,但如果完全独立地处理各指令,代码就会变得冗长而且易错。更好的方法是提取出指令之间的共同点,编写函数以减少重复代码。
vector头文件中的vector是一个不定长数组,可以用clear()清空,resize()改变大小,用push_back()和pop_back()在尾部添加和删除元素,用empty()测试是否为空。

例题5-3:安迪的第一个字典(Andy’s First Dictionary,UVa10815) P112

集合:set
输入案例:
Adventures in Disneyland

Two blondes were going to Disneyland when they came to a fork in the
road.The sign read:“Disneyland Left.”

So they went home.

#include<iostream>
#include<set>
#include<sstream>
using namespace std;
set<string> dict;
int main()
{
	/*ios::sync_with_stdio(false);*/
	freopen("input.txt", "r", stdin);
	string s, buf;
	while (cin >> s)
	{
		for (int i = 0;i < s.length();i++)
		{
			if (isalpha(s[i]))
				s[i] = tolower(s[i]);
			else
				s[i] = ' ';
		}
		stringstream ss(s);
		while (ss >> buf)
			dict.insert(buf);
	}
	for (set<string>::iterator it = dict.begin();it != dict.end();it++)
	{
		cout << *it << "\n";
	}
	return 0;
}

set就是数学上的集合——每个元素最多只出现一次。和sort一样,自定义类型也可以构造set,但同样必须定义“小于”运算符。
set::iterator,iterator的意思是迭代器,是STL中的重要概念,类似于指针。

例题5-4:反片语(Ananagrams,UVa156) P113

映射:map

#include<iostream>
#include<map>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;
map<string, int> cnt;
vector<string> words;

//将单词s进行“标准化”
string repr(const string&s)
{
	string ans = s;
	for (int i = 0;i < ans.length();i++)
	{
		ans[i] = tolower(ans[i]);
	}
	sort(ans.begin(), ans.end());
	return ans;
}

int main()
{
	freopen("input.txt", "r", stdin);
	int n = 0;
	string s;
	while (cin >> s)
	{
		if (s[0] == '#')
			break;
		words.push_back(s);
		string r = repr(s);
		if (!cnt.count(r)) cnt[r] = 0;
		cnt[r]++;//要有cnt[r]存在才会加
	}
	vector<string> ans;
	for (int i = 0;i < words.size();i++)
	{
		if (cnt[repr(words[i])] == 1)
			ans.push_back(words[i]);
	}
	sort(ans.begin(), ans.end());
	for (int i = 0;i < ans.size();i++)
	{
		cout << ans[i] << "\n";
	}
	return 0;
}

此例说明,如果没有良好的代码设计,是无法发挥STL的威力的。如果没有想到“标准化”这个思路,就很难用map简化代码。

例题5-5:集合栈计算机(The SetStack Computer,UVa12096) P115

栈:stack
输入案例:
1
5
PUSH
DUP
UNION
PUSH
ADD

#include<iostream>
#include<set>
#include<map>
#include<vector>
#include<stack>
#include<string>
#include<algorithm>
#include <iterator> //inserter的头文件
using namespace std;
typedef set<int> Set;
map<Set, int> IDcache;//把集合映射成ID
vector<Set> Setcache;//根据ID取集合

					 //查找给定集合x的ID。如果找不到,分配一个新ID
int ID(Set x)
{
	if (IDcache.count(x))
		return IDcache[x];
	Setcache.push_back(x);
	return IDcache[x] = Setcache.size() - 1;
}

#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
int main()
{
	/*ios::sync_with_stdio(false);*/
	freopen("input.txt", "r", stdin);
	int T;
	cin >> T;
	while (T--)
	{
		stack<int> s;
		int n;
		cin >> n;
		for (int i = 0;i < n;i++)
		{
			string op;
			cin >> op;
			if (op[0] == 'P')
				s.push(ID(Set()));
			else if (op[0] == 'D')
				s.push(s.top());
			else
			{
				Set x1 = Setcache[s.top()];
				s.pop();
				Set x2 = Setcache[s.top()];
				s.pop();
				Set x;
				if (op[0] == 'U')
					set_union(ALL(x1), ALL(x2), INS(x));
				if (op[0] == 'I')
					set_intersection(ALL(x1), ALL(x2), INS(x));
				if (op[0] == 'A')
				{
					x = x2;
					x.insert(ID(x1));
				}
				s.push(ID(x));
			}
			cout << Setcache[s.top()].size() << endl;
		}
		cout << "***" << endl;
	}

	return 0;
}
例题5-6:团队队列(Team Queue,UVa540) P117

队列:queue
STL队列定义在头文件中,用“queues”方式定义,用push()和pop()进行元素的入队和出队操作,front()取队首但不删除。

#include<cstdio>
#include<map>
#include<queue>
using namespace std;
const int maxt = 1000 + 10;
int main()
{
	freopen("input.txt", "r", stdin);
	int t, kase = 0;
	while (scanf("%d", &t) == 1 && t)
	{
		printf("Scenario #%d\n", ++kase);

		//记录所有人的团队编号
		map<int, int> team;
		for (int i = 0;i < t;i++)
		{
			int n, x;
			scanf("%d", &n);
			while (n--)
			{
				scanf("%d", &x);
				team[x] = i;
			}
		}

		queue<int> q, q2[maxt];
		for (;;)
		{
			int x;
			char cmd[10];
			scanf("%s", cmd);
			if (cmd[0] == 'S')
				break;
			else if (cmd[0] == 'D')
			{
				int t = q.front();
				printf("%d\n", q2[t].front());
				q2[t].pop();
				if (q2[t].empty())
					q.pop();
			}
			else if (cmd[0] == 'E')
			{
				scanf("%d", &x);
				int t = team[x];
				if (q2[t].empty())
				{
					q.push(t);
				}
				q2[t].push(x);
			}
		}
		printf("\n");
	}
	return 0;
}
例题5-7:丑数(Ugly Numbers,UVa136) P120

优先队列:priority_queue
STL的queue头文件提供了优先队列,用“priority_queue s”方式定义,用push()和pop()进行元素的入队和出队操作,top()取队首元素(但不删除,对应queue是front())。
“priority_queue pq”,pq是一个“越小的整数优先级越低的优先队列”。自定义类型也可以组成优先队列,但必须为每个元素定义一个优先级。这个优先级并不需要一个确定的数字,只需要能比较大小即可。只要元素定义了“小于”运算符,就可以使用优先队列。在一些特殊的情况下,需要使用自定义方式比较优先级,例如,要实现一个“个位数大的整数优先级反而小”的优先队列,可以定义一个结构体cmp,重载“()”运算符,使其“看上去”像一个函数,然后用“priority_queue<int,vector,cmp>pq”的方式定义。下面是这个cmp的定义:

struct cmp {
	bool operator() (const int a, const int b)const
	{//a的优先级比b小时返回true
		return a % 10 > b % 10;
	}
};

对于一些常见的优先队列,STL提供了更为简单的定义方法,例如,“越小的整数优先级越大的优先队列”可以写成“priority_queue<int,vector,greater >pq”。注意,最后两个“>”符号不要写在一起,否则会被很多(但不是所有)编译器误认为是“>>”运算符。

题意:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
丑数是指不能被2,3,5以外的其他素数整数的数。

#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<functional>//greater的头文件
using namespace std;
typedef long long LL;
const int coeff[3] = { 2,3,5 };
int main()
{
	/*ios::sync_with_stdio(false);*/
	//freopen("input.txt", "r", stdin);
	priority_queue<LL, vector<LL>, greater<LL>> pq;
	set<LL> s;
	pq.push(1);
	s.insert(1);
	for (int i = 1;;i++)
	{
		LL x = pq.top();pq.pop();
		if (i == 1500)
		{
			cout << "The 1500'th ugly number is " << x << ".\n";
			break;
		}
		for (int j = 0;j < 3;j++)
		{
			LL x2 = x*coeff[j];
			if (!s.count(x2))
			{
				s.insert(x2);
				pq.push(x2);
			}
		}
	}
	return 0;
}

不用greater定义优先级

#include<iostream>
#include<queue>
#include<vector>
#include<set>

using namespace std;
typedef long long LL;
const int coeff[3] = { 2,3,5 };
struct cmp {
	bool operator() (const LL a, const LL b) const
	{//a比b优先级小时返回true
		return a > b;
	}
};
int main()
{
	priority_queue<LL, vector<LL>, cmp> pq;
	set<LL> s;
	pq.push(1);
	s.insert(1);
	for (int i = 1;;i++)
	{
		LL x = pq.top();pq.pop();
		if (i == 1500)
		{
			cout << "The 1500'th ugly number is " << x << ".\n";
			break;
		}
		for (int j = 0;j < 3;j++)
		{
			LL x2 = x*coeff[j];
			if (!s.count(x2))
			{
				s.insert(x2);
				pq.push(x2);
			}
		}
	}
	return 0;
}
知识点一:测试STL P121

用随机序列测试sort:
为了随机生成整数,先来看看随机数发生器。核心函数是cstdlib中的rand(),它生成一个闭区间[0,RAND_MAX]内的均匀随机整数,其中RAND_MAX至少为32767(2^15-1),在不同环境下的值可能不同。严格地说,这里的随机数是“伪随机数”,因为它也是由数学公式计算出来的。
如何产生[0,n]之间的整数呢?很多人喜欢用rand()%n产生区间[0,n-1]内的一个随机整数,姑且不论这样产生的整数是否仍然分布均匀,只要n大于RAND_MAX,此法就不能得到期望的结果。另一个方法是执行rand()之后先除以RAND_MAX,得到[0,1]之间的随机实数,扩大n倍后四舍五入,得到[0,n]之间的均匀整数。这样,在n很大时“精度”不好(好比把小图放大后会看到“锯齿”),但对于普通的应用,这样做已经可以满足要求了。
srand(time(NULL))可以初始化“随机数种子”。简单地说,种子是伪随机计算的依据。种子相同,计算出来的“随机数”序列总是相同。如果不调用srand而直接使用rand(),相当于调用过一次srand(1),因此程序每次执行时,将得到同一套随机数。
想要每次得到的随机数不同,一个简单的方法是使用当前时间time(NULL)(在ctime头文件中)作为参数调用srand。time函数返回的是自UTC时间1970年1月1日0点以来经过的“秒数”,因此每秒才变化一次。如果你的程序是由操作系统自动批量执行的,可能因为每次运行的间隔时间过短,导致在相邻若干次执行时time的返回值全部相同。一个解决办法是在测试程序的主函数中设置一个循环,做足够多次测试后再退出。

#include<iostream>
#include<algorithm>
#include<vector>
#include<cstdlib>
#include<cassert>
using namespace std;
void fill_random_int(vector<int> &v, int cnt)
{
	v.clear();
	for (int i = 0;i < cnt;i++)
	{
		v.push_back(rand());
	}
}
void test_sort(vector<int>&v)
{
	sort(v.begin(), v.end());
	for (int i = 0;i < v.size() - 1;i++)
	{
		assert(v[i] <= v[i + 1]);
	}
}
int main()
{
	vector<int> v;
	fill_random_int(v, 1000000);
	test_sort(v);
	return 0;
}

测试时往往使用assert。其用法是“assert(表达式)”,当表达式为假时强行终止程序,并给出错误提示。
vector、set和map都很快,其中vector的速度接近数组(但仍有差距),而set和map的速度也远远超过了“用一个vector保存所有值,然后逐个元素进行查找”时的速度。set和map每次插入、查找和删除时间和元素个数的对数呈线性关系。
注意vector并不是所有操作都快。例如vector提供了push_front操作,但由于在vector首部插入元素会引起所有元素往后移动,实际上push_front是很慢的。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值