算法竞赛入门经典——C++与STL入门

算法竞赛入门经典——C++与STL入门

C++在string头文件里定义了string类型,直接支持流式读写。string有很多方便的函数和运算符,但速度有些慢。
考虑这样一个题目:输入数据的每行包含若干个(至少一个)以空格隔开的整数,输出每行中所有整数之和。如果只能使用字符与字符数组,一般由两种方案:一是使用getchar()边读边算,代码较短,但容易写错,并且相对较难理解;二是每次读取一行,然后再扫描该行的字符,同时计算结果。如果使用C++,代码可以很简单。

#include<iostream>
#include<string>
#include<sstream>
using namespace std;
int main()
{
	string line;
	while(getline(cin,line)){
		int sum=0,x;
		stringstream ss(line);
		while(ss>>x){
			sum+=x;
		}
		cout<<sum<<"\n";
	}
	return 0;
} 

string类在string头文件中,而stringstream在sstream头文件中。首先用getline函数读取一行数据(相当于C语言中的fgets,但由于使用string类,无须指定字符串的最大长度)然后用这一行创建一个“字符串流”——ss。接下来只需像读取cin那样读取ss即可。
虽然string和sstream都很方便,但string很慢,sstream更慢,应谨慎使用。

STL初步

排序与检索

问题:大理石在哪里(UVa 10474)

现有N个大理石,每个大理石上写了一个非负整数。首先把各数从小到大排序,然后回答Q个问题。每个问题问是否有一个大理石写着某个整数x,如果是,还要回答哪个大理石上写着x。排序后的大理石从左到右编号为1~N。(在样例中,为了节约篇幅,所有大理石上的数合并到一行,所有问题也合并到一行)

样例输入:

4 1
2 3 5 1
5
5 2
1 3 3 3 1
2 3

样例输出:

CASE# 1:
5 found at 4
CASE# 2:
2 not found
3 found at 3

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 10000;
int main()
{
	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;//在已排序数组a中寻找x 
			if(a[p]==x){
				printf("%d found at %d\n",x,p+1);
			}
			else{
				printf("%d not found\n",x);
			}
		}
	}
	return 0;
}

lower_bound的作用是查找“大于或者等于x的第一个位置”。

不定长数组:vector

vetor就是一个不定长数组。不仅如此,它把一些常用操作“封装”在了vector类型内部。例如,若a是一个vector,可以用a.size()读取它的大小,a.resize()改变大小,a.push_back()向尾部添加元素,a.pop_back()删除最后一个元素。
vector是一个模板类,所以需要用vector<int> a或者vector<double> b这样的方式来声明一个vector。vector<int>是一个类似于int a[]的整数数组,而vector<string>就是一个类似于string a[]的字符串数组。vector看上去像是“一等公民”,因为它们可以直接赋值,还可以作为函数的参数或者返回值,而无须像传递数组那样另外用一个变量指定元素个数。

集合:set

问题:安迪的第一个字典(UVa 10815)

输入一个文本,找出所有不同的单词(连续的字母序列),按字典序从小到大输出。单词不区分大小写。

分析

本题没有太多的技巧,只是为了展示set的用法:由于string已经定义了“小于”运算符,直接使用set保存单词集合即可。注意,输入时把所有非字母的字符变成空格,然后利用stringstream得到各个单词。

#include<iostream>
#include<string>
#include<set>
#include<sstream>
using namespace std;
set<string> dict;
int main()
{
	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);//放s全部赋值到ss这个流里面 
		while(ss>>buf){
			dict.insert(buf);//遇到空格截取一段放进set里 
		}
	}
	for(set<string>::iterator it=dict.begin();it!=dict.end();++it){
		cout<<*it<<"\n";
	}
	return 0;
} 

上面的代码用到了set中元素已从小到大排好序这一性质,用一个for循环即可从小到大遍历所有元素。
iterator的意思是迭代器,是STL中的重要概念,类似于指针。

映射:map

map就是从键(key)到值(value)的映射。因为重载了[ ]运算符,map像是数组的“高级版”。例如可以用一个map<string,int> month_name来表示“月份名字到月份编号”的映射,然后用month_name[“July”]=7这样的方式来赋值。

问题:反片语(UVa 156)

输入一些单词,找出满足如下条件的单词:该单词不能通过字母重排,得到文本中的另一个单词。在判断是否满足条件时,字母不分大小写,但在输出是应保留输入中的大小写,按字典序进行排序(所有大写字母在所有小写字母的前面)。

样例输入:

ladder came tape soon leader acme RIDE lone Dreis peat
ScAlE orb eye Rides dealer NotE derail LaCes drIed
noel dire Disk mace Rob dries

样例输出:

Disk
NotE
derail
drIed
eye
ladder
soon

分析:

把每个单词“标准化”,即全部转化为小写字母后再进行排序,然后再放到map中进行统计。

#include<iostream>
#include<string>
#include<cctype>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
map<string,int> cnt;
vector<string> words;
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()
{
	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]++;
	}
	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;
}

set头文件中的set和map头文件中的map分别是集合与映射。二者都支持insert、find、count和remove操作,并且可以按照从小到大的顺序循环遍历其中的元素。map还提供了“[ ]”运算符,使得map可以像数组一样使用。事实上,map也称为“关联数组”。

栈、队列与优先队列

STL还提供3种特殊的数据结构:栈、队列与优先队列。所谓栈,就是符合“后进先出”规则的数据结构,有PUSH和POP两种操作,其中PUSH把元素压入“栈顶”,而POP从栈顶把元素“弹出”。

STL的stack头文件提供了栈,用“stack s”方式定义,用push()和pop()实现元素的入栈和出栈操作,top()取栈顶元素(但不删除)。

队列

STL的queue头文件提供了队列,用“queue<int> s”方式定义,用push()和pop()进行元素的入队和出队操作,front()取队首元素(但不删除)。

优先队列

优先队列是一种抽象数据类型,行为有些像队列,但先出队列的元素不是先进队列的元素,而是队列中优先级最高的元素,这样就可以允许类似于“急诊病人插队”这样的事情发生。
STL的优先队列也定义在头文件<queue>里,用“priority_queue<int> pq”来声明。这个pq是一个“越小的整数优先级越低的优先队列”。由于出队元素并不是最先进队的元素,出队的方法由queue的front()变为top()。
自定义类型也可以组成优先队列,但必须为每个元素定义一个优先级。这个优先级并不需要一个确定的数字,只需要能比较大小即可。看到这里,是不是想起了sort?没错,只要元素定义了“小于”运算符,就可以使用优先队列。在一些特殊情况下,需要使用自定义方式比较优先级,例如,要实现一个“个位数大的整数优先级反而小”的优先队列,可以定义一个结构体cmp,重载“()”运算符,使其“看上去”像一个函数,然后用“priority_queue<int,vector<int>,cmp> pq”的方式定义。下面是这个cmp的定义:

struct cmp{
	bool operator()(const int a,const int b) const{
true
	return a % 10 > b % 10;
	}
};

对于一些常见的优先队列,STL提供了更为简单的定义方法,例如,“越小整数优先级越大的优先队列”可以写成“priority_queue<int,vector<int>,greater<int>> pq”。注意,最后两个“>”符号不要写在一起,否则会被很多(但不是所有)编译器误认为是“>>”运算符。
STL的queue头文件提供了优先队列,用“priority_queue<int> s”方式定义,用push()和pop()进行元素的入队和出队操作,top()取队首元素(但不删除)。

问题:丑数(UVa 136)

丑数是指不能被2,3,5以外的其他素数整除的数。把丑数从小到大排列起来,结果如下:
1,2,3,4,5,6,8,9,10,12,15,…
求第1500个丑数。

分析

本题的实现方法有很多种,这里仅提供一种,即从小到大生成各个丑数。最小的丑数是1,而对于任意丑数x,2x、3x和5x也都是丑数。这样,就可以用一个优先队列保存所有已生成的丑数,每次取出最小的丑数,生成3个新的丑数。唯一需要注意的是,同一个丑数有多种生成方式,所以需要判断一个丑数是否已经生成过。

#include<iostream>
#include<vector>
#include<queue>
#include<set>
using namespace std;
typedef long long LL;
const int coeff[3]={2,3,5};
int main()
{
	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;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值