5.5 习题
做题做题做题
5-1 代码对齐 (UVA 1593)
输入若干行代码,要求各列的单词的左边界对齐并且尽量靠左。单词之间至少空一格。
样例输入:
␣␣start:␣␣integer;␣␣␣␣//␣begins␣here
stop:␣integer;␣//␣␣ends␣here
␣s:␣␣string;
c:␣␣␣char;␣//␣temp
样例输出:
start:␣integer;␣//␣begins␣here
stop:␣␣integer;␣//␣ends␣␣␣here
s:␣␣␣␣␣string;
c:␣␣␣␣␣char;␣␣␣␣//␣temp
(中间就代表空格)
分析:这个问题并不困难(大概?),我们将这若干个字符串分割出来的结果看作字符串的一个二维数组,那列是什么呢?就是这若干个字符串中分割出来最多字符串的数量。
代码实现如下:
#include<iostream>
#include<cstring>
#include<sstream>
#include<vector>
using namespace std;
vector<string>split_string[1010];//用于存放被分割的字符串
int width[200];//打印时每一列的宽度
int main(){
string s; int num=0,maxn=0;//maxn表示若干个字符串中,被分割出最多的子字符串数量
while (getline(cin,s)){
stringstream SS(s); string p; num++;//分割字符串
while (SS>>p){
split_string[num].push_back(p);}
if (maxn<split_string[num].size()) maxn=split_string[num].size();
}
}
maxn就是最大的分割数量。
接下来我们只需要一个width元素用于存放每一列的宽度,width[i]表示这若干个字符串分割后的第i个字符串的集合中,字符串的最大长度+1(+1表示空格)。
接下来我们只需要在输出时,按照第i个字符串对应的宽度数组的元素表示域宽进行输出即可。
代码比较简单就不做实现了(我懒)。
5-2 Ducci序列 (UVA 1594)
对于一个n元组(a1,a2······an),可以对于每个数求出它和下一个数的的差的绝对值,得到一个新的n元组(|a1-a2|,|a2-a3|······|an-a1|),重复这个过程得到的序列称为Ducci序列。
有的Ducci序列最后会循环,有的会全部变为0。你的任务就是判断它最终会成为0还是会循环。
分析:没什么好说的就硬模拟的一个问题。
但是在模拟的过程中,我发现建立一个以vector为元素的set还是比较困难,于是我将数组通过stringstream转化为字符串,然后建立一个以string为元素的set。
首先是转化函数:
string num_string(){
//将数字转化为字符串
string ans="";
//stringstream可以转化为string,sprintf转化为的是字符数组或字符指针数组
for (int i=0;i<n;i++){
stringstream ss; string s;
ss<<a[i]; ss>>s; ans+=s; if (i!=n-1) ans+="0";
//中间添加0,防止一些奇怪的数据重复
}
return ans;
}
但是在循环的可能性外,还有一种可能,就是全为0所以我们还需要一个判定整个数组是否全部为0的函数:
//判定是否全部为0的函数
bool is_allzero(string s){
for (int i=0;i<s.size();i++) if (s[i]!='0') return false; return true;}
这里中间添加的0的好处就体现出来了。
完整代码如下:
#include<iostream>
#include<sstream>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
int n,a[40],b[40];
set<string>data;//转化为字符串的Ducci序列
string num_string(){
//将数字转化为字符串
string ans="";
//stringstream可以转化为string,sprintf转化为的是字符数组或字符指针数组
for (int i=0;i<n;i++){
stringstream ss; string s;
ss<<a[i]; ss>>s; ans+=s; if (i!=n-1) ans+="0";
//中间添加0,防止一些奇怪的数据重复
}
return ans;
}
//判定是否全部为0的函数
bool is_allzero(string s){
for (int i=0;i<s.size();i++) if (s[i]!='0') return false; return true;}
int main(){
cin>>n; for (int i=0;i<n;i++) cin>>a[i];
while (true){
string x=num_string();
if (is_allzero(x)){
cout<<"ZERO";break;}//全为0的情况
if (data.count(x)){
cout<<"LOOP";break;}//循环
for (int i=0;i<n;i++) b[i]=abs(a[i]-a[(i+1)%n]);
for (int i=0;i<n;i++) a[i]=b[i];//b就是a序列的Ducci序列
data.insert(x);
}
return 0;
}
自己写的代码果然就很丑。
5-3 卡牌游戏 (UVA 10935)
桌上有n张牌,从第一张牌(即位于顶面的牌)开始,从上往下依次编号为1-n。当至少还剩下两张牌时进行以下操作:把第一张牌扔掉,然后把新的第一张牌放到整叠牌的最后。输入一个n,然后依次输出每次扔掉的牌以及最后剩下的牌。
分析:有"顶面",也有"牌底"。这个很明显就是要用队列了。
没什么好多说的,非常简单的问题啦:
#include<iostream>
#include<queue>
using namespace std;
int main(){
int n; cin>>n;
queue<int>card; for (int i=1;i<=n;i++) card.push(i);//对排队进行准备
cout<<"Discarded cards:";
while (card.size()>=2){
cout<<card.front()<<" "; card.pop();//将第一张牌拿出
card.push(card.front()); card.pop();//第一张牌放到底部
}
cout<<endl<<"Remaining card:"<<card.front(); return 0;
}
5-4 交换学生 (UVA 10763)
有n个学生想要交换到其他学校学习,规定每个想从A学校换到B学校的学生必须找一个想从B换到A的"搭档"。如果每个人都能找到搭档(一个人只能找一个搭档),学校就会同意他们交换。每个学生用两个整数A,B表示,你的任务是判断交换是否可行。
分析:感觉这个题也是map的一个简单应用。对于一个学生选择切换到学校A->B,如果B->A在map中存在,那么可以建立一对搭档,如果不存在,就在map中建立一个新的key:A->B。
实现代码如下:
#include<iostream>
#include<map>
#include<cstring>
#include<sstream>
using namespace std;
string num_string(int a,int b){
//将两个数字转化为字符串
stringstream ss,sss; string s,ans="";
ss<<a; ss>>s; ans+=s; ans+="x"; sss<<b; sss>>s; ans+=s;
return ans;
}
map<string,int>stu_id;//学生到对应学校的映射
int main(){
int n,x,y; cin>>n;
for (int i=0;i<n;i++){
cin>>x>>y; string s1=num_string(x,y),s2=num_string(y,x);
if (!stu_id.count(s1)&&!stu_id.count(s2)) stu_id[s1]=1;//初始化映射
else if(stu_id.count(s1)) stu_id[s1]++;//x->y的又多一个
else stu_id[s2]--;//找到搭配
}
bool flag=true;//接下来就把映射中的value全部遍历一遍即可
for (map<string,int>::iterator i=stu_id.begin();i!=stu_id.end();i++)
if(i->second){
flag=false;break;}
(flag)?cout<<"YES":cout<<"NO";
}
比较简单的问题就适合比较粗糙的做法。
5-5 复合词 (UVA 10391)
给出一个词典,找出所有的复合词,即恰好有两个单词拼接而成的单词。输入每行都是一个由小写字母组成的单词。输入已按照字典序从小到大排序,且不超过120000个单词。输出所有复合词,按照字典序从小到大排列。
分析:首先看到这道题,你应该是可以想到至少两种思路的,第一种合成词,将n个单词按照n*n的复杂度的算法两两合成,然后用set进行判断,不用想这个肯定不行,120000的数据肯定做不了这么大的工作量。还有一种想法就是把一个单词拆成很多对小单词,然后用set进行判断,假设单词的最大长度为m,那么这个问题的时间复杂度就是n*m。
我开始是非常怀疑我自己想错了的,后来网上搜了以下,发现那些通过的人就是用第二种方法做的,实际m的数据比较小。
那么这个问题就非常简单了,下面请看代码:
#include<set>
#include<iostream>
using namespace std;
const int N=120005;
int main(){
set<string>S;//用于存放字符串的集合
string s[N]; int t=0; while (cin>>s[t]){
S.insert(s[t]);t++;}
for (int i=0;i<t;i++){
for (int j=1;j<s[i].size();j++){
//将字符串进行拆分,然后分别查找
string s1=s[i].substr(0,j),s2=s[i].substr(j,s[i].size()-j);
if (S.count(s1)&&S.count(s2)) {
cout<<s[i]<<endl;break;}
}
}
return 0;
}
5-6 对称轴 (UVA 1595)
给出平面上N个点,问是否可以找到一条竖线,使得所有点左右对称。
分析:看了一下网上的解法,什么冒泡排序,二分查找,各种杂七杂八的解法。按照我自己的思路,首先在对称的图中,所有纵坐标相同的在竖线两边的点,肯定要对称。也就是说我们将这些点的横坐标相加一定等于对称轴的横坐标乘以纵坐标相同的点的个数,明白了这个,这个问题我们就比较好解决了。
代码如下:
#include<map>
#include<iostream>
using namespace std;
int main(){
map<int,int>y_num,y_tot;//分别表示该y坐标的点的个数和x坐标和
int n,x,y; cin>>n; bool flag=true;
for(int i=0;i<n;i++){
cin>>x>>y;
if (!y_num.count(y)) {
y_num[y]=1;y_tot[y]=x;}//对新出现的纵坐标建立联系
else{
y_num[y]++; y_tot[y]+=x;} //进行演变
}
int aver=y_tot.begin()->second/y_num[y_tot.begin()->first];//aver就是坐标轴的横坐标
for (map<int,int>::iterator i=y_tot.begin();i!=y_tot.end();i++)
if (i->second/y_num[i->first]!=aver){
flag=false;break;}
(flag)?cout<<"YES":cout<<"NO";
}
这里的遍历方法可能比较复杂,希望大家无比搞清楚是怎么运作的。
5-7 打印队列
学生会里只有一台打印机,但同时有很多文件需要打印,因此打印任务不可避免地需要等待。有些打印任务比较急,有些不那么急,每个任务都有1-9的优先级,优先级越高表示任务越急。
打印机的运作方式如下:首先从打印队列里取出一个任务J,如果队列里有比J更急的任务,则直接把J放到打印队列尾部,否则打印任务J(此时不会把它放回打印队列)。
输入打印队列中各个任务的优先级以及所关注的任务在队列中的位置(队首位置为0)。输出该任务完成的时刻。所有的任务都需要1分钟打印。例如:打印队列为{1,1,9,1,1,1},目前处于队首的任务最终完成时刻为5。
分析:首先这个题很明显也是用队列去实现的。就是说当前队列的队首如果不是整个队列优先级最高的就将队首pop然后放到这个队列的队尾,这个模拟过程是不困难的。
可是还有一个问题,我们怎么比较快速的得到当前优先级最高的元素呢?还记得当时学习stl方面的知识时,有一个容器叫做优先队列(priority_queue)么?我们使用优先队列就可以比较简单的得到当前轮次队列中的最高优先级。
代码实现如下:
#include<iostream>
#include<queue>
using namespace std;
int main(){
//pos表示访问的元素在队列的位置,m为"被观察"的元素在队列的位置
int n,m,x,pos=0; cin>>n>>m; queue<int>q; priority_queue<int>pq;
for (int i=0;i<n;i++) {
cin>>x;q.push(x);pq.push(x);}
while (true){
//优先队列的队首和队列队首相等时,表示这个任务的优先度最高可以做
if (pq.top()==q.front()){
//此时减去队列长度就可以得到已经打印的任务多少
if (pos==m) {
cout<<n-q.size()+1; break;}
else {
pq.pop();q.pop();m++;}//m++就是访问下一个元素
}
else{
//将队首的元素拿出放入队尾,如果这个元素恰好是我们需要访问,m和pos的值会发生改变
int a=q.front(); q.pop(); q.push(a);
if (pos==m){
pos=0;m=q.size()-1;} else pos++;
}
}
return 0;
}
5-8 图书管理系统 (UVA 230)
分析:就是让你去模拟一个图书管理系统。首先输入若干图书的标题和作者,这个输入的格式就非常的ex啊,其中标题名首尾分别带着双引号,格式为标题名+by+作者名。这里默认所有的书都是放在书架上的。
接下来是若干行指令,其中一共分为三种类型的指令:BORROW指令(BORROW+标题名):表示借这本书,我们这里的情况类似于虚拟了一个用户在不断地进行借书。RETURN指令(RETURN+标题名):表示归还这本书,但是并没有第一时间将这本书放到架子上。SHELVE指令(就单独一个SHELVE):表示将所有已经归还但还没有放到架子上的书,进行排序后依次插入书架并输出图书标题和插入的位置(插入的位置可能是第一本书或是某本书后面,如果是后面一种情况,输出的格式为还的书的标题名+after+前面一本书的标题名)。
根据上面题面的解读我们可以确定的是,对于一本书有三种属性,一个是书的标题名,一个是书的作者名,还有一个是书的状态(BORROW,RETURN,SHELVE)。其中书的状态,我们通过map建立映射,然后还需要一个结构体表示书。
代码实现如下:
struct book{
//表示书的结构体
char title[99],author[99];
friend bool operator<(book a,book b){
//为后面的排序做准备
if (strcmp(a.author,b.author)) return strcmp(a.author,b.author)<0;//先比较作者名
else return strcmp(a.title,b.title)<0;//再比较标题名
}
}Book[1111];
map<string,string>book_state;//建立书和书目前状态的映射
接下来对书进行输入,代码如下:
char buf[99],book_title[99]; int book_size=0; book_state.clear();
while (gets(buf)&&strcmp(buf,"END")){
//以我的经验,这里千万不要用getline
int position=strchr(buf+1,'"')-buf;//从下标为1的字符开始,以"作为分割的标准得到书的标题名
strncpy(Book[book_size].title,buf,position+1);
position=strstr(buf+position,"by")-buf;//以by为标准得到书的作者名
strcpy(Book[book_size].author,buf+position+3);//这里是+3,因为在作者名中我们不需要by
book_state[Book[book_size].title]="SHELVE"; book_size++;//将架子上书的初始状态设置为SHELVE
}
sort(Book,Book+book_size);//对架子上的书排序
三种指令,BORROW指令和RETURN指令都比较简单,就是对状态进行一下修改:
BORROW指令和RETURN指令:
if ('B'==buf[0]){
//BORROW指令,将书的状态改为BORROW
strcpy(book_title,buf+strlen("BORROW ")); book_state[book_title]="BORROW";
}else if ('R'==buf[0]){
//RETURN指令,将书的状态改为RETURN
strcpy(book_title,buf+strlen("RETURN ")); book_state[book_title]="RETURN";
}
SHELVE指令就比较困难了,我在自己做的时候,卡在了怎么找上一本书的问题上,这里提供了一个蛮好的解决方法:
while (gets(buf)&&strcmp(buf,"END")){
//输入指令
if('B'==buf[0]){
//BORROW指令,将书