在一个有限的正整数序列中,有些数会多次重复出现。请你统计每个数的出现次数,然后按数字在序列中第一次出现的位置顺序输出数及其次数。
输入格式:
第1行,1个整数N,表示整数的个数,(1≤N≤50000)。
第2行,N个正整数,每个整数x 都满足 1 ≤ x ≤2000000000。
输出格式:
若干行,每行两个用一个空格隔开的数,第一个是数列中出现的数,第二个是该数在序列中出现的次数。
输入样例:
输出样例:
题意:
统计序列中各元素出现的频率,并按照原来的相对顺序依次输出。
解法一:
统计频率:
由于题目中有规定整数的个数,可以开辟一个对应大小的数组,每次读入数组的下标,将对应的元素+1,用空间换时间。
按照原相对顺序输出:
我们可以采用一个容器来保存之前出现过的元素下标,最后再按照原顺序输出。由于先入容器的下标要先出容器,所以我们容器我们选择队列。
解法二:
解法一:
#include "iostream" #include "queue" using namespace std; queue<int> q; int Num; int a[500000];//全局变量,初始即全为0 int main() { cin>>Num; int index; for(int i=0;i<Num;i++) { cin>>index; if(!a[index])//若a[index]为零,说明index第一次出现,index入栈 q.push(index); a[index]++; } while(q.size()!=1)//由于输出格式有要求,我们在队列剩余一个元素时停止输出 { index=q.front(); q.pop(); cout<<index<<" "<<a[index]<<endl; } index=q.front();//最后一个元素输出时最后无多余回车 q.pop(); cout<<index<<" "<<a[index]; }
解法二:
#include "iostream" #include "algorithm" #include "queue" using namespace std; int a[50001]; int N; queue<int> q; int main() { cin>>N; for(int i=1;i<=N;i++) { cin>>a[i]; if(find(a,a+i,a[i])==a+i)//检测是否a[i]之前是否存在,这个算法就是遍历,效率不高 q.push(a[i]); } sort(a+1,a+N+1); int val; while(q.size()!=1)//由于输出格式有要求,在队中元素剩一个时停止输出 { val=q.front(); q.pop(); cout<<val<<" "<<upper_bound(a+1,a+N+1,val)-lower_bound(a+1,a+N+1,val)<<endl; } val=q.front(); q.pop(); cout<<val<<" "<<upper_bound(a+1,a+N+1,val)-lower_bound(a+1,a+N+1,val); }
n个人围成一圈,从1开始依次编号,做报数游戏。 现指定从第1个人开始报数,报数到第m个人时,该人出圈,然后从其下一个人重新开始报数,仍是报数到第m个人出圈,如此重复下去,直到所有人都出圈。总人数不足m时将循环报数。请输出所有人出圈的顺序。
输入格式:
一行,两个整数n和m。n表示游戏的人数,m表示报数出圈的数字,1≤n≤50000,1≤m≤100。
输出格式:
一行,n个用空格分隔的整数,表示所有人出圈的顺序。
输入样例:
输出样例:
题意:
经典的约瑟夫环问题。
由于算法需要高效,因此采用链表结构。构建具有哨兵结点的循环链表,每次出圈便去掉一个结点,直到结点全部去除。
#include "iostream" using namespace std; struct cell { int data; cell *next; }; int N; int M; int main() { cin>>N>>M; cell *head,*tmp,*tail; head=tail=new cell;//创建哨兵结点 for(int i=0;i<N;i++)//构建单向链表 { tmp=new cell; tmp->data=i+1; tail->next=tmp; tmp->next=NULL; tail=tmp; } tail->next=head->next;//单向链表成环 tmp=head;//以下tmp为要删除(出圈)的结点,p为tmp的前驱结点 cell *p=NULL; while(N!=1)//由于格式有要求,所以在N=1时停止输出 { for(int i=0;i<M;i++)//寻找要删除的结点 { p=tmp; tmp=tmp->next; } cout<<tmp->data<<" ";//输出删除的结点 p->next=tmp->next;//删除结点 //delete tmp;//这里怕多次调用delete超时,所以没写 tmp=p;//将tmp重置到前一位 N--;//人数-1 } cout<<tmp->next->data<<endl;//输出最后的结点 //注意tmp指向的是上一个删除节点的前驱,要删除的结点为上一个删除结点的后继,因此删除的是tmp->next->data }
算术表达式按中缀给出,以=号结束,包括+,-,*,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。
计算过程中,如果出现除数为0的情况,表达式的结果为"NaN" ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。 输入保证表达式正确。
输入格式:
一行,包括1个算术表达式。算术表达式的长度小于等于1000。
输出格式:
一行,算术表达式的值 。
输入样例:
输出样例:
题意:
计算中缀表达式的值,除数为0时,需要特别处理。
我们需要两个栈,一个为数字栈,用来储存遇到的数字,一个为符号栈,用来储存遇到的符号。
与最原始的一位数字计算不同,本题中的数字不仅仅为一位数字。当读到数字字符时,可以循环读入数字字符,并根据sum=sum*10+(now-‘0’)来算出该大于一位的数字。
解决大于一位的数字之后,接着要考虑如何计算表达式。有两种思路:①先将中缀表达式转换成后缀表达式,再计算后缀表达式(与后缀表达式相关内容可见本文最后)。②边读边计算。这里主要介绍第二种方法。
当读入数字时,直接压入数字栈即可。当遇到符号时,有四种情况:①遇到左括号,直接入栈(因为左括号表示当前位置后面的数字要先算出来,所以此处不需要任何操作);②遇到±*/,不断取栈顶符号,如果栈顶符号的优先级大于或等于当前符号的优先级,则处理栈顶符号(因为若栈顶符号的优先级不小于当前符号,说明栈顶符号此时已经可以计算了)(具体过程为取数字栈的两个数据,检测符号,做相应的运算后,将结果再压入数字栈);③遇到右括号,不断取栈顶符号,并处理,直到遇到左括号停止;④遇到结束符号(本题为等号),不断处理栈顶符号即可(因为之前已经将能计算的全部计算,最后符号栈中符号的优先级关系一定符合正常运算)。
需要注意的问题
遇到四种运算符时,一定要循环处理前面遇到的优先级大于或等于其的运算符,不要只处理优先级大于其的运算符,否则有反例:计算1-2-3,由于采用栈,所以计算顺序为倒序,则程序的计算过程为1-2-3=1-(-1)=2显然是错的。
具体演示,计算4-2*3-(3-4-5)=的值(由于*为特殊字符,演示中用×代替)
#include "iostream" #include "map" #include "stack" using namespace std; map<char,int> Pro;//用来储存符号优先级 stack<int> Num;//数字栈 stack<char> Oper;//符号栈 //用来记录大于1位整数的变量 int Flag;//某种标志 int s;//最终的整数值 inline void Calculate(char oper)//根据符号进行计算,并将结果入栈 { int x,y; x=Num.top(); Num.pop(); y=Num.top(); Num.pop(); if(oper=='+') Num.push(y+x); else if(oper=='-') Num.push(y-x); else if(oper=='*') Num.push(y*x); else if(oper=='/') { if(!x)//除数为0 { cout<<"NaN"; exit(0); } Num.push(y/x); } } int main() { //建立符号间的优先级 Pro.insert(make_pair('#',-1)); Pro.insert(make_pair('(',0)); Pro.insert(make_pair('+',1)); Pro.insert(make_pair('-',1)); Pro.insert(make_pair('*',2)); Pro.insert(make_pair('/',2)); Oper.push('#');//压栈符号 char ch; cin>>ch; while(ch!='=') { while(ch>='0'&&ch<='9')//处理大于1位整数 { s=s*10+ch-'0'; Flag=1;//标志此时检测到了数字 cin>>ch; } if(Flag)//之前检测到了数字,压入数字,Flag和s归位 { Num.push(s); Flag=0; s=0; } if(ch=='=')//如果数字之后是‘=’直接退出循环 break; if(ch=='(')//左括号优先级最低,直接入栈 Oper.push(ch); else if(ch==')')//检测到右括号,不断计算,直到遇到左括号 { char oper=Oper.top(); while(oper!='(') { Oper.pop(); Calculate(oper); oper=Oper.top(); } Oper.pop();//弹出左括号 } else if(ch=='+'||ch=='-'||ch=='*'||ch=='/')//检测到四种运算符处理前面所有优先级不大于ch的符号 { char oper=Oper.top(); while(Pro[oper]>=Pro[ch]) { Oper.pop(); Calculate(oper); oper=Oper.top(); } Oper.push(ch);//压入当前符号 } cin>>ch; } while(Oper.size()!=1)//处理剩余的符号,直到遇到压栈符号'#' { char oper=Oper.top(); Oper.pop(); Calculate(oper); } cout<<Num.top(); }
小唐这段时间在研究序列。拿来N个整数的序列,他给序列中的每个整数都赋予一个喜爱值。喜爱值也是整数,有正有负,越大表明越喜欢。他想知道,如何从序列中连续取最多m个数,他获得喜爱值最大。1≤N≤500000,1≤m≤N。
输入格式:
第一行是两个整数N,m。分别代表序列中数的个数以及能取的最多个数。
第二行用空格隔开的N个整数,第i个整数Li代表他对第i个数的喜爱值。│Li│≤1000。
输出格式:
一行,三个数,表示获得最大喜爱值,及第一个取最大喜爱值的区间。
输入样例:
输出样例:
题意:
输出 至多m长度的区间内 和的 最大值。
本题是一个典型的利用单调队列求解的题目。
维护一个区间正确,且单调递增的队列,每次队首都是当前区间的最小值。
#include "iostream" using namespace std; struct Cell { int data;//记录元素值 int num;//记录下标 }; class MyDeque//自定义双端队列 { Cell a[500001]; int Num;//队列中元素的个数,用来给Cell中的num赋值用 int top;//队列头指针 int rear;//队列尾指针 public: MyDeque():top(0),rear(0),Num(0){} void push(int x)//后端入队 { a[rear].data=x; a[rear].num=++Num; rear++; } int peek_backdata(){ return a[rear-1].data; }//查看队尾元素值 int peek_backnum(){ return a[rear-1].num; }//查看队尾下标 void pop_back(){ rear--; }//后端出队 int peek_frontdata(){ return a[top].data; }//查看队头元素值 int peek_frontnum(){ return a[top].num; }//查看队头下标 void pop_front(){ top++; }//前端出队 int empty(){ return top==rear; }//判断队空 }; int M,N; int a[500001];//实际储存前n项的和,由于是全局变量,a[0]初始时为0 MyDeque d; int Max=-500000001;//最大值,开始时初始化为最小值 int top=1;//左区间 int rear=1;//右区间 int main() { cin>>N>>M; for(int i=1;i<=N;i++) { cin>>a[i];//依次读入数据 a[i]+=a[i-1];//记录前n项和,方便以后求某区间内的和 } for(int i=0;i<N;i++) { //队列非空,不断检测队尾元素,弹出大于当前元素的结点 while(!d.empty()&&d.peek_backdata()>a[i]) d.pop_back(); d.push(a[i]);//当前元素入队 //检测队头元素的下标,维护在要求的区间内 while(d.peek_backnum()-d.peek_frontnum()>=M) d.pop_front(); int tmp=a[i+1]-d.peek_frontdata();//计算当前区间内的元素之和 if(Max<tmp)//判断是否更新最大值 { Max=tmp; top=d.peek_frontnum(); rear=d.peek_backnum(); } } cout<<Max<<" "<<top<<" "<<rear; }
一种map的用法——处理优先级。此处map的用法类似于一种特殊的数组,数组的下标可以是任意类型的值,数组的值也可以是任意类型的值。
与第三题相关的题目——中缀表达式转后缀表达式
参考代码:
#include "iostream" #include "vector" #include "map" #include "stack" using namespace std; vector<string> ans;//储存后缀表达式 stack<char> Oper;//符号栈 map<char,int> pro;//处理优先级 inline string String(char c)//char类型转string类型 { string s(1,c); return s; } int main() { //设置优先级 pro.insert(make_pair('+',0)); pro.insert(make_pair('-',0)); pro.insert(make_pair('*',1)); pro.insert(make_pair('/',1)); pro.insert(make_pair('(',-1)); char str[21]; int i=0; cin>>str; int len=strlen(str); while(i<len) { int Flag=0; char tmp=str[i]; string s; while(tmp>='0'&&tmp<='9')//处理多位数字 { s=s+tmp; i++; tmp=str[i]; Flag=1; } if(Flag)//数字直接入栈 ans.push_back(s); //按照运算顺序,符号依次入栈 if(tmp=='(') Oper.push(tmp); else if(tmp==')') { char oper=Oper.top(); while(oper!='(') { Oper.pop(); ans.push_back(String(oper)); oper=Oper.top(); } Oper.pop(); } else { char oper; while(!Oper.empty()) { oper=Oper.top(); if(pro[oper]>=pro[tmp]) { Oper.pop(); ans.push_back(String(oper)); } else break; } Oper.push(tmp); } i++; } while(Oper.size()!=1)//将剩余的等号以外的符号入栈 { char oper=Oper.top(); Oper.pop(); ans.push_back(String(oper)); } for(vector<string>::iterator it=ans.begin();it!=ans.end();it++) cout<<*it<<" "; cout<<endl; return 0; }