1027 火星颜色
本题本质上是把3个十进制数字转化为13进制下的数字输出。由于输入范围在0-168之间,只需把十进制数字除以13和对13取余后的结果输出即可。
#include<cstdio>
void transferRadix(int a){
if(a/13<=9){
printf("%d",a/13);
}else{
int x=a/13-10;
printf("%c",'A'+x);
}
if(a%13<=9){
printf("%d",a%13);
}else{
int x=a%13-10;
printf("%c",'A'+x);
}
}
int main(){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
printf("#");
transferRadix(a);
transferRadix(b);
transferRadix(c);
return 0;
}
当然,还可以把13进制转化为数组,根据下标对应输出更简洁。
#include<cstdio>
char radix[14]={"0123456789ABC"};
int main(){
printf("#");
for(int i=0;i<3;i++){
int num;
scanf("%d",&num);
printf("%c%c",radix[num/13],radix[num%13]);
}
return 0;
}
语法:字符串数组,可以直接用字符串赋值,不必一个个字符赋值
1021 最深的根
(1)要判断图是否有环,有环的话需要判断有几个连通分量;可以用深度遍历算法,访问完所有结点调用DFS函数的次数即为连通分量个数
(2)若无环,则要找到使树具有最大深度的根节点并按序输出;如果仅调用一次DFS,则是树,进行第二次遍历,逐步更新找到最大深度,并将可能的根结点存入set中。
#include<cstdio>
#include<set>
#include<cstring>
using namespace std;
const int maxn=10010;
int G[maxn][maxn]={0};
bool isFind[maxn]={false};
int n,maxDepth=1;
set<int> root;
void DFS2(int u,int depth){
//printf("访问结点%d,深度为%d\n",u,depth);
isFind[u]=true;//u结点被访问
if(depth==maxDepth) {
root.insert(u);
}else if(depth>maxDepth){
root.clear();
maxDepth=depth;
root.insert(u);
}
for(int v=1;v<=n;v++){
if(G[u][v]==1&&isFind[v]==false){//u可以到达v,且v没被访问
DFS2(v,depth+1);
}
}
}
int main(){
scanf("%d",&n);
int a,b;
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
G[a][b]=G[b][a]=1;
}
int k=0;
for(int u=1;u<=n;u++){
if(isFind[u]==false){
DFS2(u,1);
k++;
}
}
//printf("k=%d\n",k);
root.clear();
if(k>1){
printf("Error: %d components",k);
return 0;
}
for(int u=1;u<=n;u++){
memset(isFind,0,sizeof(isFind));
/*
for(int i=1;i<=n;i++){
isFind[i]=false;
}
*/
DFS2(u,1);
}
//printf("maxDepth=%d\n",maxDepth);
for(set<int>::iterator it=root.begin();it!=root.end();it++){
printf("%d\n",*it);
}
return 0;
}
为保证每个结点都能按最大深度方式遍历到,每次要重置遍历数组,不过这导致我在某个测试点超时。如何更简便的找到这些合适的根结点呢?
这里使用了一个结论:先任意选择一个点作为根节点,设其能到达最深的顶点集合为A,再从A中任选一个作为根节点,设其能到达最深的顶点集合为B,则树高最大顶点为A和B的集合。
#include<cstdio>
#include<set>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=10010;
int G[maxn][maxn]={0};
bool isFind[maxn]={false};
int n,maxDepth=1;
vector<int> temp;//存储临时可能根节点
set<int> root;
void DFS(int u,int depth){
//printf("访问结点%d,深度为%d\n",u,depth);
isFind[u]=true;//u结点被访问
if(depth==maxDepth) {
temp.push_back(u);
}else if(depth>maxDepth){//更新最大深度和对应结点
temp.clear();
maxDepth=depth;
temp.push_back(u);
}
for(int v=1;v<=n;v++){
if(G[u][v]==1&&isFind[v]==false){//u可以到达v,且v没被访问
DFS(v,depth+1);
}
}
}
int main(){
scanf("%d",&n);
int a,b;
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
G[a][b]=G[b][a]=1;
}
int k=0,s1;
for(int u=1;u<=n;u++){
if(isFind[u]==false){
DFS(u,1);
if(u==1){
if(temp.size()!=0) s1=temp[0];//取当前深度最深结点中任意一个作为下次遍历的根节点
for(int i=0;i<temp.size();i++){//把当前深度最大结点集合加入root
root.insert(temp[i]);
}
}
k++;
}
}
if(k>1){
printf("Error: %d components",k);
return 0;
}
memset(isFind,0,sizeof(isFind));//重置访问数组
temp.clear();
maxDepth=0;
DFS(s1,1);//再次调用,确定集合B
for(int i=0;i<temp.size();i++){//把集合B也加入到root中
root.insert(temp[i]);
}
for(auto it=root.begin();it!=root.end();it++){
printf("%d\n",*it);
}
return 0;
}
语法:(1)注意memset和fill的用法区别,memset是按字节赋值,如
memset(isFind,0,sizeof(isFind))
fill则是对一个个数组单元赋值:
fiil(isFind,ifFind+maxn,false);
(2)使用set可以自动排序去重,有时比vector更方便,其访问可以直接用auto,不必写复杂的迭代器
106 多项式乘法(1009)
多项式乘法可以分为两步,(1)逐项相乘;(2)合并同类项
可以定义多项式结构体存储多项式,再读入第2个多项式每一项时进行相乘;
合并过程,可以建立一个map,从指数映射到其系数
输出时要注意:(1)先去除不为0的项;(2)map是升序的,所以要倒序输出
#include<cstdio>
#include<map>
using namespace std;
struct polynomial{
int exp;
double coef;
}pol[10],p2;
map<int,double> result;
int main(){
int k1,k2;//多项式项数
scanf("%d",&k1);
for(int i=0;i<k1;i++){//读入第一个多项式
scanf("%d%lf",&pol[i].exp,&pol[i].coef);
}
int Exp;
double Coef;
scanf("%d",&k2);
for(int i=0;i<k2;i++){//读入第二个多项式
scanf("%d%lf",&p2.exp,&p2.coef);
//printf("p2指数项为%d,系数项为%.1lf\n",p2.exp,p2.coef);
for(int j=0;j<k1;j++){
Exp=p2.exp+pol[j].exp;
Coef=p2.coef*pol[j].coef;
//printf("指数项为%d,系数项为%.1lf\n",Exp,Coef);
auto it=result.find(Exp);
if(it==result.end()){//该项第一次出现
result[Exp]=Coef;
}else{
result[Exp]+=Coef;
}
}
}
map<int,double>::reverse_iterator it;
for(it=result.rbegin();it!=result.rend();it++){
if(it->second==0){//先去除系数为0的项
result.erase(it->first);
}
}
printf("%d",result.size());
for(it=result.rbegin();it!=result.rend();it++){
printf(" %d %.1lf",it->first,it->second);
}
return 0;
}
语法:反向遍历map的方法:
map<int,double>::reverse_iterator it;
for(it=result.rbegin();it!=result.rend();it++)
当然,注意到本题最大系数仅为1000,相乘后不会超过2000,也可以用辅助存储结果,输出其中不为0的项即可
#include<cstdio>
using namespace std;
const int maxn=1001;
int main(){
int n1,n2;
double arr[maxn]={0.0},ans[2*maxn]={0.0};//结果数组最大可达到2000,要多开内存
scanf("%d",&n1);
int exp;
double coef;
for(int i=0;i<n1;i++){
scanf("%d%lf",&exp,&coef);
arr[exp]=coef;
}
scanf("%d",&n2);
for(int j=0;j<n2;j++){
scanf("%d%lf",&exp,&coef);
for(int i=0;i<maxn;i++){
ans[i+exp]+=arr[i]*coef;
}
}
int cnt=0;
for(int i=0;i<2*maxn;i++){
if(ans[i]!=0.0) cnt++;
}
printf("%d",cnt);
for(int i=2*maxn-1;i>=0;i--){
if(ans[i]!=0.0){
printf(" %d %.1lf",i,ans[i]);
}
}
return 0;
}
107 乒乓球(1026)
本题要求模拟排队打乒乓球的过程。难点在于有VIP球桌和球员。
思路:
1、建立球员和球桌的结构体,表明其时间信息和是否是VIP;
2、读入球员和球桌信息,球员按抵达时间排序;
3、按球员到来顺序和VIP条件分配桌子,分以下情况讨论:
设最早空闲桌子为idx,
1)如果idx是VIP桌,且当前队首为VIP球员,则idx给它
2)如果idx是VIP桌,当前队首不是VIP,则要看在idx空闲前,是否后续有VIP球员抵达,若有,让VIP插队获得idx
3)如果idx是普通桌,当前队首不是VIP,idx给它
4)如果idx是普通桌,当前队首是VIP,则要在这名VIP球员到达前,检查是否又有VIP桌子空闲,若有,则VIP球员去VIP桌,否则,去idx。
从上面看出,球员到达队列需要两个指针,一个i指向队首,一个VIPi指向下一个VIP球员
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cmath>
using namespace std;
const int INF=100000000;
const int maxk=110;
struct Player{//球员
int arriveTime,startTime,serveTime;//抵达时间、开始训练时间、训练时间
bool isVIP;//是否是VIP
};
vector<Player> player;//由于想要直接把21点和来的运动员直接排出,所以不用数组而用vector
struct Table{
int endTime,numServe=0;//当前服务结束时间,服务人数
bool isVIP;
}table[maxk];
bool cmpArriveTime(Player x,Player y){
return x.arriveTime<y.arriveTime;
}
bool cmpStartTime(Player x,Player y){
return x.startTime<y.startTime;
}
int nextVIPPlayer(int VIPi){//寻找下一个VIP球员
VIPi++;//先从当前VIP往后已一个
while(VIPi<player.size()&&player[VIPi].isVIP==0){
VIPi++;
}
return VIPi;
}
//把球桌pID分给球员tID
void allotTable(int pID,int tID){
if(player[pID].arriveTime<=table[tID].endTime){//球员先到,更新其开始时间为桌子空闲时间
player[pID].startTime=table[tID].endTime;
}else{//球桌先空
player[pID].startTime=player[pID].arriveTime;
}
//更新球桌训练结束时间,服务人数+1
table[tID].endTime=player[pID].startTime+player[pID].serveTime;
table[tID].numServe++;
}
int main(){
int n;
scanf("%d",&n);
int hh,mm,ss,Times,trainTime,isVIP;
int stTime=8*3600,edTime=21*3600;//开门和闭馆时间
Player newplayer;
for(int i=0;i<n;i++){
scanf("%d:%d:%d %d %d",&hh,&mm,&ss,&trainTime,&isVIP);
Times=hh*3600+mm*60+ss;
if(Times>=edTime) {
//printf("Times=%d\n",Times);
continue;//闭馆时间后才来,直接跳过,不考虑
}
newplayer.arriveTime=Times;
newplayer.startTime=edTime;//先初始化开始时间为21点
if(trainTime>120){//训练时间大于2h,压缩成2h
trainTime=120*60;
}else{
trainTime*=60;
}
newplayer.serveTime=trainTime;
newplayer.isVIP=isVIP;
player.push_back(newplayer);//加入球员队列中
}
//printf("球员数%d\n",player.size());
int k,m;//球桌数和VIP桌数
scanf("%d%d",&k,&m);
int VIPTable;//VIP桌子的号码
for(int i=0;i<m;i++){//记录VIP桌子
scanf("%d",&VIPTable);
table[VIPTable].isVIP=true;
}
sort(player.begin(),player.end(),cmpArriveTime);//按到达时间对球员排序
int i=0,VIPi=-1;//i指向当前队首球员,VIPi指向当前最前的VIP球员
VIPi=nextVIPPlayer(VIPi);
while(i<player.size()){//遍历球员列表
int idx=-1,minEndTIme=INF;
for(int j=1;j<=k;j++){
if(table[j].endTime<minEndTIme){//寻找当前最早空闲的桌子idx
idx=j;
minEndTIme=table[j].endTime;
}
}
if(table[idx].endTime>=edTime) break;//已到关门时间,结束
if(player[i].isVIP==1&&i<VIPi){
//i号是VIP球员,但VIPi>i,说明它已经安排过了
i++;
continue;
}
//分4种情况讨论
if(table[idx].isVIP==1){
if(player[i].isVIP==1){//情况1:球员和球桌都是VIP
allotTable(i,idx);
if(VIPi==i) VIPi=nextVIPPlayer(VIPi);//找到下一个VIP球员
i++;
}else{//情况2球桌都是VIP,球员不是VIP
//如果当前队首的VIP球员在VIP空闲前到,则插队获得
if(VIPi<player.size()&&player[VIPi].arriveTime<=table[idx].endTime){
allotTable(VIPi,idx);
VIPi=nextVIPPlayer(VIPi);
}else{
allotTable(i,idx);
i++;
}
}
}else{
if(player[i].isVIP==0){//情况3:球桌不是VIP,球员不是VIP
allotTable(i,idx);
i++;
}else{//情况4:球桌不是VIP,球员是VIP
//找到最早空闲VIP球桌
int VIPidx=-1,minVIPEndTime=INF;
for(int j=1;j<=k;j++){
if(table[j].isVIP==1&&table[j].endTime<minVIPEndTime){
minVIPEndTime=table[j].endTime;
VIPidx=j;
}
}
//若最早空闲VIP球桌,且空闲时间比球员来的时间早,则分给它
if(VIPidx!=-1&&player[i].arriveTime>=table[VIPidx].endTime){
allotTable(i,VIPidx);
if(VIPi==i) VIPi=nextVIPPlayer(VIPi);
i++;
}else{
allotTable(i,idx);
if(VIPi==i) VIPi=nextVIPPlayer(VIPi);
i++;
}
}
}
}
sort(player.begin(),player.end(),cmpStartTime);
for(int i=0;i<player.size()&&player[i].startTime<edTime;i++){
int t1=player[i].arriveTime,t2=player[i].startTime;
printf("%02d:%02d:%02d ",t1/3600,t1%3600/60,t1%60);
printf("%02d:%02d:%02d ",t2/3600,t2%3600/60,t2%60);
printf("%.0f\n",round((t2-t1)/60.0));//服务时间
}
for(int i=1;i<=k;i++){
printf("%d",table[i].numServe);
if(i<k) printf(" ");
}
return 0;
}
108 找平均数(1108)
本题要求一组数字的平均数,关键在于筛除非法输入。合法的输入要求:最多只能有两位小数的数字。先用字符串读入输入数字,再审查每一位(除了负号)),看是否是0-9之间的数字,如果有小数点,要求后面数字不能超过2位。之后再将合法数字从字符串转化为数字存储计算。
#include<cstdio>
#include<vector>
using namespace std;
vector<double> num;
bool isLegal(char s[]){
int i=0;
int decimal=0,numPoint=0;//统计小数点后位数和小数点个数
if(s[i]=='-') i++;
while(s[i]!='\0'){
if(s[i]>='0'&&s[i]<='9'){
if(numPoint==1) decimal++;
if(decimal>2) return false;
}
else if(s[i]=='.'){
numPoint++;
if(numPoint>1) return false;
}
else{
return false;
}
i++;
}
return true;
}
double change(char s[]){
int i=0;
double ans=0;
if(s[i]=='-') i++;
while(s[i]!='\0'&&s[i]!='.'){
ans=ans*10+s[i]-'0';
i++;
}
if(s[i]=='.'){
i++;
if(s[i]!='\0') ans+=0.1*(s[i]-'0');
if(s[++i]!='\0'){
//printf("%s有2位小数,i=%d\n",s,i);
ans+=0.01*(s[i]-'0');
}
}
if(s[0]=='-') ans*=-1;
return ans;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
char str[100];
scanf("%s",str);
if(isLegal(str)){//合法数字
double a=change(str);
if(a>1000||a<-1000) {
printf("ERROR: %s is not a legal number\n",str);
continue;
}
num.push_back(a);
}else{
printf("ERROR: %s is not a legal number\n",str);
}
}
if(num.size()==0){
printf("The average of 0 numbers is Undefined");
}else{
double sum=0;
int size=num.size();
for(int i=0;i<size;i++){
//printf("%.2lf\n",num[i]);
sum+=num[i];
}
if(size==1){
printf("The average of %d number is %.2lf",size,sum/size);
}
else {
printf("The average of %d numbers is %.2lf",size,sum/size);
}
}
return 0;
}
也可以用sscanf和sprintf解决
#include<cstdio>
#include<string.h>
using namespace std;
int main(){
int n,cnt=0;
scanf("%d",&n);
char a[50],b[50];
double sum=0,temp;
for(int i=0;i<n;i++){
scanf("%s",a);
sscanf(a,"%lf",&temp);//读入字符串a中的浮点数
sprintf(b,"%.2f",temp);//把浮点数temp取2位小数,存入字符串b
int flag=0;
for(int j=0;j<strlen(a);j++){//排出小数点多余2位的情况
if(a[j]!=b[j]) {
flag=1;
break;
}
}
if(flag||temp<-1000||temp>1000){
printf("ERROR: %s is not a legal number\n",a);
continue;
}else{
sum+=temp;
cnt++;
}
}
if(cnt==1){
printf("The average of 1 number is %.2lf",sum);
}else if(cnt>1){
printf("The average of %d numbers is %.2lf",cnt,sum/cnt);
}else{
printf("The average of 0 numbers is Undefined");
}
return 0;
}
语法:(1)sscanf的用法:sscanf() - 从一个字符串中读进与指定格式相符的数据.
如:取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。
sscanf("123456abcdedfBCDEF", "%[1-9a-z]", buf);
printf("%s\n", buf);
结果为:123456abcdedf
(2)sprintf的用法:
int sprintf( char *buffer, const char *format, [ argument] … );
参数列表:
buffer:char型指针,指向将要写入的字符串的缓冲区。
format:char型指针,指向的内存里面存放的将要格式字符串。
[argument]...:可选参数,可以是任何类型的数据。
返回值:字符串长度(strlen)
109 集体照(1109)
本题要根据身高确定每个人在集体照中的位置。思路:
(1)定义结构体存储每个人的信息,之后按身高和姓名顺序排序;
(2)每个人的位置信息由行号和列号确定。根据总人数和行数可以确定每行人数,在根据规则确定每行位置即可。
#include<cstdio>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn=10010;
struct Man{
char name[10];
int height;//身高
int No;//在集体照中的序号
}man[maxn];
bool cmpHeight(Man x,Man y){//按身高和姓名排序
if(x.height!=y.height) return x.height>y.height;
else return strcmp(x.name,y.name)<0;
}
bool cmpNo(Man x,Man y){
return x.No<y.No;
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++){
scanf("%s %d",man[i].name,&man[i].height);
}
sort(man,man+n,cmpHeight);
int extra=n%k;//每行人数和最后一行多余人数
int No=0,cnt=0;//集体照中的序号,已遍历的前i-1行人数
for(int i=0;i<k;i++){
int rowNum=n/k;//正常每行人数
if(i==0) rowNum+=extra;//最后一行加上多余人数
int mid=rowNum/2+1;//当前行队中元素时第几位
for(int j=0;j<rowNum;j++){
if(j%2==0) man[No++].No=cnt+mid+j/2;//排在中间元素的右边
else man[No++].No=cnt+mid-(j+1)/2;//排在中间元素的左边
}
cnt+=rowNum;
}
sort(man,man+n,cmpNo);
No=0;
for(int i=0;i<k;i++){
int rowNum=n/k;//正常每行人数
if(i==0) rowNum+=extra;//最后一行加上多余人数
for(int j=0;j<rowNum;j++){
if(j>0) printf(" ");
printf("%s",man[No].name);
No++;
}
printf("\n");
}
return 0;
}
110 完全二叉树(110)
本题要判断给定的树是否是完全二叉树。
(1)用静态树存储结点信息,未被读到的结点即为树的根结点
(2)深度遍历,确定树的高度h,之后层序遍历统计1-h-1层的结点数是否符合要求,最后一层是否是顺序铺满的。从而判断是否是完全二叉树.
但是用这样的方法显得很繁琐。注意到完全二叉树的性质,设根节点序号为i(从1开始)则左子树序号为2*i,右子树为2*i+1。用这样的方法给树结点编号,若最后序号大于节点数,则说明不是完全二叉树。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=25;
struct Node{
int lchild,rchild;
}node[maxn];
bool isRoot[maxn]={false};
int maxi=0,last;//统计最大序号和对应结点序号
void DFS(int root,int i){
if(i>maxi){
maxi=i;
last=root;
}
if(node[root].lchild!=-1) DFS(node[root].lchild,2*i);
if(node[root].rchild!=-1) DFS(node[root].rchild,2*i+1);
}
int main(){
int n;
scanf("%d",&n);
string a,b;
for(int i=0;i<n;i++){
cin>>a>>b;
if(a=="-"){
node[i].lchild=-1;
}else{
node[i].lchild=stoi(a);
isRoot[stoi(a)]=1;
}
if(b=="-"){
node[i].rchild=-1;
}else{
node[i].rchild=stoi(b);
isRoot[stoi(b)]=1;
}
}
int root;
for(int i=0;i<n;i++){
if(isRoot[i]==false) {
root=i;
break;
}
}
DFS(root,1);
if(maxi>n){
printf("NO %d",root);
}else{
printf("YES %d",last);
}
return 0;
}
语法:stoi():把数字字符串转变为int输出,头文件#include<cstring>,参数为string类型
类似的还有atoi(),参数是char* str
112 卡住的键盘(112)
本题要求判断键盘上卡住的键,按1次会重复输出k次。将判断的键按出现先后顺序输出,
并把去掉重复后的结果输出。
(1)寻找坏键。将每个字符与前一个字符相比较,如果相同,cnt++,继续比较;否则置为1
cnt%k==0,说明是坏键,因为有的键可能本身就被连按了几次。
考虑到有些字母开始被认为是坏键,后面发现不是,所以建立一个map<char,bool>的映射存储
相应的:还有前面认为不是坏键则它一定没坏,后面如果出现大量重复也不能认为出现了坏键,
所以也要用sureNoBroken标记出来。
(2)输出坏键,由于需要按发现顺序输出,所以要遍历字符串,输出确定是坏键的字符,同时,输出后存入set中,避免重复输出。
#include<cstdio>
#include<set>
#include<map>
#include<string.h>
using namespace std;
bool sureNoBroken[256]={false};
map<char,bool> mp;
set<char> printed;
int main(){
int k;
scanf("%d",&k);
char s[1010];
scanf("%s",s);
int len=strlen(s);
char pre='#';
int cnt=1;
for(int i=0;i<len;i++){
if(s[i]==pre){//出现重复,开始统计
cnt++;
}else{//不同
if(cnt%k!=0){//cnt不能整除k,所以pre一定不是坏键
//printf("cnt=%d,pre=%c\n",cnt,pre);
sureNoBroken[pre]=true;
}
cnt=1;
}
mp[s[i]]=(cnt%k==0);//先把初步判断结果存入mp
pre=s[i];
}
for(int i=0;i<len;i++){
if(sureNoBroken[s[i]]==true){//确定不是快键的要修正
mp[s[i]]=false;
}
}
for(int i=0;i<len;i++){//输出坏键
if(mp[s[i]]&&printed.find(s[i])==printed.end()){//mp中确定是坏键,且还未输出过
printf("%c",s[i]);
printed.insert(s[i]);//输出过的保存,避免重复
}
}
printf("\n");
for(int i=0;i<len;i++){
printf("%c",s[i]);
if(mp[s[i]]){//坏键跳过重复部分
i=i+k-1;
}
}
return 0;
}