https://vjudge.net/problem/UVA-10474
题意:现有N个大理石,每个大理石上写了一个非负整数,首先把各数从小到大排序,然后回答Q个查询问题,查询整数x是否存在,若存在给出位置。排序后的大理石从左到右编号为1-N
本题是《算法竞赛入门经典》的例题5-1,是一道排序的水题。
先排序,再查找即可。查找可以直接遍历,或者二分查找,在这里采用的是STL中的sort函数和针对有序序列的lower_bound函数。
lower_bound返回一个非递减序列[first, last)中的第一个大于等于值val的位置
upper_bound返回一个非递减序列[first, last)中第一个大于val的位置。
它们的本质也是二分查找,如图所示(http://www.cnblogs.com/cobbliu/archive/2012/05/21/2512249.html)
(注意返回值:对于数组而言返回的是相对于数组首部偏移后的地址,对于vector返回的是该位置的迭代器)
因此,先调用sort对输入数组做升序排序,再调用lower_bound,如果返回值有效则输出。
#include
#include
using namespace std;
const int MAXN = 10000;
int main()
{
int N, Q, a[MAXN], x;
int kase = 0;
while (cin >> N >> Q && N)
{
cout << "CASE# " << ++kase << ":\n";
for (int i = 0; icin >> a[i];
sort(a, a+N); //升序
while (Q--)
{
cin >> x;
int p = lower_bound(a, a+N, x) - a; //在非递减序列[First,Last)中找到第一个大于等于x的位置,注意用法
if (a[p] == x) printf("%d found at %d\n", x, p+1); //找到了x的位置
else printf("%d not found\n", x);
}
}
return 0;
}
https://vjudge.net/problem/UVA-101
题意:从左到右有n个木块,编号为0~n-1,要求模拟以下4种操作(下面的a和b均为编号)
move a onto b:把a和b上方的木块全部归到原位,然后把a放在b上面。
move a over b:把a上面的木块全部归位,然后把a放在b所在堆的顶部。
pile a onto b:把b上面的木块全部归位,然后把a及上面的木块整体堆在b的上面。
pile a over b:把a及上面的木块整体放在b所在堆的顶部。
遇到quit结束操作并输出堆的现状,a和b在同一堆时是非法指令,不操作。
本题是《算法竞赛入门经典》的例题5-2,是一道vector+模拟的水题。
由于每个堆的高度不固定具有可变性,因此用vector数组(每个元素是一个vector)来表示每个堆是很合理的。对于vector,主要就是push_back,pop_back,size(访问大小),resize(调整大小)等函数的调用。
先找到给定数字a和b所在的堆及其高度,根据指令的含义分别操作。
这里比较巧妙的是,刘汝佳为了避免冗余的代码,把4种if判断和操作合并为3句话,统一用pile_upon函数实现移动,原因是对于a的堆,单纯move a等价于先把a之上的木块归位,再pile_upon一个a即可,否则移动多个;另一边,如果有onto要把b上面的归位,否则都是放在b堆的顶部。
#include
#include
#include
#include
using namespace std;
const int maxn = 30;
vector<int> pile[maxn]; //最多有maxn个堆,每个堆都是一个vector
int n;
void find_block(int num, int &p, int &h) //找到编号为num的堆p及其高度h,修改引用
{
for (int i = 0; i < n; ++i)
for (int index = 0; index < pile[i].size(); ++index)
{
if (pile[i][index] == num)
{
p = i; h = index;
return;
}
}
}
void clear_above(int p, int h) //把第p堆在高度h之上的木块移回原位
{
for (int i = h+1; i < pile[p].size(); ++i)
{
int x = pile[p][i];
pile[x].push_back(x); //归位
}
pile[p].resize(h+1); //只保留下标0~h的元素
}
void pile_upon(int p1, int h1, int p2) //把p1堆中高度h1及其之上的木块整体移动到p2顶部
{
for (int i = h1; i < pile[p1].size(); ++i)
pile[p2].push_back(pile[p1][i]);
pile[p1].resize(h1); //只保留下标0~h-1的元素
}
void print_status() //打印输出
{
for (int i = 0; iprintf("%d:", i);
for (int j = 0; j < pile[i].size(); ++j)
printf(" %d", pile[i][j]);
printf("\n");
}
}
int main()
{
cin >> n;
string s1,s2;
int a, b;
for (int i = 0; i//堆初始化
while(cin >> s1 >> a >> s2 >> b)
{
int pa, pb, ha, hb;
find_block(a, pa, ha); //找到数字a
find_block(b, pb, hb); //找到数字b
if (pa == pb) continue; //非法指令
//合并后的3句话操作
if (s1 == "move") //要move a一定会归位a上面的木块
clear_above(pa, ha);
if (s2 == "onto") //要onto b一定会归位b上面的木块
clear_above(pb, hb);
pile_upon(pa, ha, pb); //把a及其之上的木块移到b堆上
/*合并前
if (s1 == "move" && s2 == "onto")
{
clear_above(pa, ha);
clear_above(pb, hb);
pile[pb].push_back(a);
pile[pa].pop_back();
}
else if (s1 == "move" && s2 == "over")
{
clear_above(pa, ha);
pile[pb].push_back(a);
pile[pa].pop_back();
}
else if (s1 == "pile" && s2 == "onto")
{
clear_above(pb, hb);
pile_upon(pa, ha, pb);
}
else if (s1 == "pile" && s2 == "over")
pile_upon(pa, ha, pb);
*/
//print_status();//观察每一次移动
}
print_status();
return 0;
}
https://vjudge.net/problem/UVA-10815
输入一个文本,找出所有不同的单词(不区分大小写),按字典序从小到大输出。
本题是《算法竞赛入门经典》的例题5-3,不需要算法和技巧,主要是熟悉STL中集合对象set的操作。
由于要求每个单词只出现一次,而set容器内部由红黑树实现,插入删除查找的效率都非常高,而且是自动排序、去重,所以用set存放单词是非常合理的。
注意输入时将所有非字母的字符变为空格,用字符串流stringstream得到各个单词。
#include
#include
#include
#include
using namespace std;
set<string> dict; //string集合
int main()
{
string s, buf;
while(cin >> s)
{
for (int i = 0; iif (isalpha(s[i])) //统一为小写
s[i] = tolower(s[i]);
else s[i] = ' ';
stringstream ss(s); //构造字符串流
while (ss >> buf)
dict.insert(buf); //新word加入词典
}
for (auto it = dict.begin(); it != dict.end(); ++it) //遍历输出
cout << *it << endl;
return 0;
}
https://vjudge.net/problem/UVA-156
题意:给定一个文本,找出不能在这个文本里找到重组词的所有单词(重新对字符排序组合,比如NOTE的重组词可以是TONE,但是QUIZ没有重组词)
本题是《算法竞赛入门经典》的例题5-4,是一道排序的基础题,简单复习一下map。
思路和之前的POJ 2159 Ancient Cipher(字符串排列替换)有些类似。
由于重组词的组合方式有很多种,没必要一个个列举出来(暴力列举也可以,STL里的函数next_permutation提供排列组合),只需要对文本里每个字符串排序并建立map计数,如果一个字符串排序后的出现次数至少2次,说明这个字符串存在重组词。
需要注意每次插入map的是小写化并排序后的单词,输出是最后无重组词的源字符串要按字典序输出。
#include
#include
#include
#include
#include
#include
using namespace std;
map<string, int> dict; //存放单词出现的次数,int默认为0
vector<string> input; //存放输入的源字符串
string trans(const string &s) //单词标准化
{
string ret = s;
for (int i = 0; i//小写化
ret[i] = tolower(ret[i]);
sort(ret.begin(), ret.end()); //string内部排序
return ret;
}
int main()
{
string s;
while (cin >> s)
{
if (s == "#") break;
input.push_back(s);
dict[trans(s)]++; //标准化后的单词出现次数加一
}
vector<string> res;
for (auto it = input.begin(); it != input.end(); ++it) //遍历输入字符串
{
if (dict[trans(*it)] == 1) //如果只出现一次,说明只有它本身,没有重组词
res.push_back(*it);
}
sort(res.begin(), res.end()); //升序输出
for (auto it = res.begin(); it != res.end(); ++it)
cout << *it << endl;
return 0;
}
https://vjudge.net/problem/UVA-12096
题意:设计一个专门为集合运算使用的“集合栈”,栈中的每个元素都是栈,所有操作都对栈顶集合进行,支持以下操作:
PUSH:将空集 {} 入栈
DUP:将栈顶元素复制一份后入栈
UNION:出栈两个集合,将两者的并集入栈
INTERSECT:出栈两个集合,将两者的交集入栈
ADD:出栈两个集合,将先出栈的集合加入到后出栈的集合中,并把新集合入栈
每次操作后输出栈顶集合的基数大小(即元素个数),不考虑空栈、越界。
本题是《算法竞赛入门经典》的例题5-5,是一道容器的综合应用题。
(由于本题的操作对象嵌套,自己实现起来有点难,下面都是对书上内容的总结,注意下面的“大”、“小”二字是相对的)
本题的集合并不是简单的整数集合或者字符串集合,而是小集合的大集合,为了方便起见,为每个不同的集合(大或小)分配一个唯一的ID,每个大集合都表示为它所包含小集合对应ID的大集合。所以用STL的set
表示这个大集合(是不是很迷乱- -),整个栈就是一个stack
(每个大集合也有个ID啊)。
建立双射关系:对任意集合x,map对象IDcache[x]就是x的ID,vector对象Setcache[IDcache[x]]就是集合x本身。
所有操作都对栈顶的大集合进行,push、dup、加操作需要用到ID,交、并操作需要用到集合本身。
需要注意的是
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x, x.begin())
typedef set<int> Set;
//map和vector形成双射
mapint > IDcache; //把集合映射为ID
vector Setcache; //下标ID对应一个集合
stack<int> s; //题目中要求的栈,每个元素为集合对应的ID
int getID(Set x) //返回集合x对应的ID
{
if (IDcache.count(x) != 0) //已经分配了ID
return IDcache[x];
//插入新的集合
Setcache.push_back(x);
return IDcache[x] = Setcache.size()-1; //返回该集合对应的下标作为ID
}
int main()
{
int kase;
cin >> kase;
while(kase--)
{
int n;
cin >> n;
for (int i = 0; istring op;
cin >> op;
if (op[0] == 'P') s.push( getID( Set() ) ); //插入空集
else if (op[0] == 'D') s.push(s.top()); //复制栈顶集合
else
{
//找到栈顶ID对应的两个集合
Set x1 = Setcache[s.top()]; s.pop();
Set x2 = Setcache[s.top()]; s.pop();
Set tmp; //存放操作结果的集合,和宏定义INS配合
if (op[0] == 'U') //并集
set_union(ALL(x1), ALL(x2), INS(tmp)); //前四个参数为两个集合的首尾迭代器,最后一个参数为插入到新集合的指定位置
else if (op[0] == 'I') //交集
set_intersection(ALL(x1), ALL(x2), INS(tmp));
else if (op[0] == 'A') //把第一个集合作为ID插到第二个集合
{
tmp = x2;
tmp.insert(getID(x1));
}
s.push(getID(tmp)); //新集合入栈
}
cout << Setcache[s.top()].size() << endl; //栈顶ID对应集合的基数
}
cout << "***" << endl;
}
return 0;
}
https://vjudge.net/problem/UVA-540
题意:有team_cnt个团队的人要排队,每次新来一个人的时候,如果他的队友在排队,他就排到这个团队的后面,否则他排到整个队伍的队尾。输入团队数、每个团队中所有队员的编号,要求支持ENQUEUE, DEQUEUE, STOP三种操作,对于每个DEQUEUE指令,输出出队的人的编号。
本题是《算法竞赛入门经典》的例题5-5,是一道哈希思想和queue+map的综合应用题。
最直观的想法就是建立队列的队列,即queue
,每个正在q里排队的都是一个队列,然后每次入队时遍历q,如果q[i]的第一个人是队友就查到i的后面。
想法看起来很对,但是至少存在两个问题:
因此书上提出了一种利用哈希访问正在排队的团队的方法,即队列q本身不记录每个团队的排队情况,而是将团队代表的ID值入队或出队,由此访问团队为ID的排队情况q2[ID]。
map
将队员映射到其所属团队编号,入出队的逻辑如下:
需要注意的是,由于每个case中map和q、q2都要重新初始化,所以不能放在全局。
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1005;
int main()
{
int team_cnt, x, cnt, kase = 0;
while (cin >> team_cnt && team_cnt)
{
printf("Scenario #%d\n", ++kase);
//为每个人分配对应的团队ID
map<int,int> TeamID;
for (int i = 0; i < team_cnt; ++i)
{
cin >> cnt;
while(cnt--)
{
cin >> x;
TeamID[x] = i; //存放x所属的团队ID
}
}
//q是团队编号的队列(横向),q2[i]是团队i(如果)排队的人员队列(纵向)
queue<int> q, q2[maxn]; //不能放在全局
//执行操作
string op;
while (cin >> op && op != "STOP")
{
int num;
if (op[0] == 'E') //入队
{
cin >> num;
int t = TeamID[num];
if (q2[t].empty()) //如果团队t没人在排队
q.push(t); //团队t排进队列
q2[t].push(num); //团队t加上这个人
}
else if (op[0] == 'D') //出队
{
int t = q.front();
//队列首部的团队出一个人
cout << q2[t].front() << endl;
q2[t].pop();
//队列首部的团队没人了,去除这个团队
if (q2[t].empty()) q.pop();
}
}
cout << endl;
}
return 0;
}
https://vjudge.net/problem/UVA-136
丑数是指不能被2,3,5以外的素数整除的数,即它的质因数只能是2,3,5,把所有丑数从小到大排序,求第1500个丑数。
如1,2,3,4,5,6,8,9,10,12,15…
这道题是《算法竞赛入门经典》的例题5-7,看上去有关数论,但是其实没有那么复杂,也不用打素数表来遍历查表之类的。
如果不从遍历每个数字判断的角度,而是从产生规则的角度看,最小的丑数是1,而对于任何丑数x,2x、3x、5x也都是丑数,这样就可以用一个优先队列保存所有已经产生的丑数,每次取出最小的丑数衍生出3个丑数,加入优先队列(优先队列默认最大堆,因此要用greater
参数声明最小堆优先队列)
由于一个丑数可以有多种产生方式(比如12,15),所以用集合set表示一个数是否被产生过,只有没被产生过时才加入优先队列。
#include
#include
#include
#include
#include
using namespace std;
typedef long long LL;
const int base[] = {2,3,5};
int main()
{
priority_queuevector, greater > pq;
set s; //集合标记某个数字是否出现过
s.insert(1);
pq.push(1);
int cnt = 0;
while (1)
{
LL x = pq.top(); pq.pop(); //取出队首的数
cnt++; //弹出一个数字+1,直到弹出第1500个
if (cnt == 1500)
{
printf("The 1500'th ugly number is <%I64d>.", x);
break;
}
LL tmp;
for (int i = 0; i<3; ++i)
{
tmp = x*base[i]; //产生2x,3x,5x
if (s.count(tmp) == 0) //没有出现过
{
s.insert(tmp); //标记tmp出现过
pq.push(tmp); //tmp插入队列
}
}
}
return 0;
}