1. 计算a+b(1001)
本题a,b的范围都是在正负1000000以内,数值不大,因此不必担心int溢出的问题,可以直接相加。难点在于输出,每隔三位要插入“,”。
我最开始是用int保存结果,通过存储%1000后的结果输出,但这样对一些特殊情况就通过不了。比如两个1000000相加,结果为200000,要准确控制输出几个0就很不方便。因此我改用string存储结果并输出,就解决了这一问题。
#include<iostream>
#include<string>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
int sum=a+b;
if(sum<0){
//负数先处理符号问题,再转成正数,便于之后同一处理
cout<<"-";
sum*=-1;
}
//to_string()函数把其他类型转化成string,记得添加<string>头文件
string s=to_string(sum);
int n=s.length()%3;//n用来控制在第几位时要输出
for(int i=0;i<s.length();i++){
cout<<s[i];
if((i+1-n)%3==0&&i<s.length()-1) cout<<",";
}
return 0;
}
语法收获:(1)测试用例输入a\b时,中间只能用空格,不能用逗号,否则会影响cin>>a>>b中b的读入;
(2)字符串转化函数to_string()。
2.洗牌机(1042)
这题的难点就在于如何正确地对数组操作,进行多次洗牌了。最直接的想法当然是先定义一个顺序的牌数组,再根据洗牌序列洗牌。但这样做的问题在于:定义一个char数组存储牌就比较费劲,洗牌时还要大量交换,更是繁琐。
但既然原牌组本身是有序的,可以直接用其序号代替其内容(花色与点数),对序号数组洗牌,根据最终洗牌的结果序列来输出即可。
如果想避免代码段中t1还是t2存储结果的判断带来的代码冗余,可以增加一段代码,每次洗牌后把t1结果都给t2。
#include<iostream>
using namespace std;
int main(){
char flower[6]={"SHCDJ"};
int K,num=0,i;
int order[54],t1[54],t2[54];
//由于涉及多次洗牌,使用两个t数组轮流存储洗牌结果
cin>>K;
//完成第一次洗牌,num表示原牌组中的序号
while(cin>>order[num]){
t2[order[num]-1]=num+1;
num++;
}
//完成剩余k-1次洗牌
int flag=2;//现在是t2存储洗牌结果
for(i=0;i<K-1;i++){
if(flag==2) {
for(num=0;num<54;num++){
t1[order[num]-1]=t2[num];
}
flag=1;
}
else{
for(num=0;num<54;num++){
t2[order[num]-1]=t1[num];
}
flag=2;
}
}
if(flag==1){
for(i=0;i<54;i++){
//s,n分别表示对应的花色和点数,对13点的情况要特别处理一下
int s=t1[i]/13;
int n=t1[i]%13;
if(n==0) {n=13;s--;}
cout<<flower[s]<<n;
if(i!=53)cout<<" ";
}
}
else{
for(i=0;i<54;i++){
int s=t2[i]/13;
int n=t2[i]%13;
if(n==0) {n=13;s--;}
cout<<flower[s]<<n;
if(i!=53)cout<<" ";
}
}
return 0;
}
3. 最短路径(1046)
本题是判断环路上两点间的最短路径,我们只需要考虑从i-j和j-i的长度,选出其中最小值即可,但是常规的递增计算会超时。经过查看参考答案,解决方法是在读入数据时,定义辅助数组存储第i个点到第1个点的距离。这样,两点间的一个距离就可以直接用对应数组相减即可。
#include<iostream>
using namespace std;
#define N 100000
#define M 10000
int main(){
int n,m,i,sum=0;
int dis[N];
int p1[M],p2[M];
cin>>n;
for(i=0;i<n;i++){
cin>>dis[i];
sum+=dis[i];
}
cin>>m;
for(i=0;i<m;i++){
cin>>p1[i]>>p2[i];
}
int t,j;
for(i=0;i<m;i++){
int d=0;
if(p1[i]>p2[i]){
t=p1[i];
p1[i]=p2[i];
p2[i]=t;
}
if(p2[i]-p1[i]<n/2){
for(j=p1[i];j<p2[i];j++){
d+=dis[j-1];
}
}
else{
for(j=p2[i];j<=n;j++){
d+=dis[j-1];
}
for(j=0;j<p1[i];j++){
d+=dis[j-1];
}
}
if(d<sum-d) cout<<d<<endl;
else cout<<sum-d<<endl;
}
return 0;
}
4.A+BandC(1046)
这题难度较大,因为加数较大,容易溢出。开始考虑用string操作,但这样还得分+和-考虑,计算2遍,非常繁琐。参考网上答案,要注意到输入数据范围在[-2^63,2^63]范围内,考虑用long long存储(范围是[-2^63,2^63))。若a<0,b<0时sum>0,必定溢出,A+B肯定小于C;同理,a>0,b>0,sum<0,也一定溢出,a+b肯定大于C;不溢出时,正常比较即可。
但现在系统上增加了2^63的测试用例,用long long读入就会自动溢出变成-2^63,导致错误,这是需要后面继续解决的问题。
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main(){
int T,i;
cin>>T;
for(i=1;i<=T;i++){
long long A,B,C,sum;
cin>>A>>B>>C;
sum=A+B;
if(A>0&&B>0&&sum<0){
//sum<0说明溢出了,sum一定大于C
cout<<"Case #"<<i<<": true"<<endl;
}
else if(A<0&&B<0&&sum>0){
//sum>0说明溢出了,sum一定小于C
cout<<"Case #"<<i<<": false"<<endl;
}
else if(sum>C) {
cout<<"Case #"<<i<<": true"<<endl;
}
else {
cout<<"Case #"<<i<<": false"<<endl;
}
}
return 0;
}
语法:(1)注意几种类型的数据范围:如对4字节的int,共有32位,所以范围是[-2^31,2^31-1]
5. 多项式加法(1002)
本题难度不大,主要在于输出细节上的注意。要描述一个多项式,即要指明其每项的系数和指数,指数都是自然数,正好对应数组的下标,所以只需用数组存储每项的系数,加法操作直接对对应的系数值执行加减即可。注意以下问题:
(1)如何统计多项式和有几个非0项?我最开始是想根据读入数据时,读入了哪些项,结果就有几项。但这样忽略了相加后系数为0的情况,比如-1.5x和1.5X+2相加,结果只有1项。所以应当在相加结束后,再来统计多项式项数;
(2)输出格式的问题:不要在末尾多加空格,不应该在输出项数时就打印空格,这样会在项数为0时出错(这个输出错误找了好久o(╥﹏╥)o);
(3)审题,输出应该只保留一位小数。
#include<iostream>
#include <iomanip>
using namespace std;
#define N 1001
int main(){
double coe[N]={0};
int max=0,min=N,num=0;//存储最大次数
int i,k;
for(i=0;i<2;i++){
cin>>k;
int exp;
double coeff;
for(int j=0;j<k;j++){
cin>>exp>>coeff;
coe[exp]+=coeff;
//cout<<j<<" "<<coe[exp]<<endl;
if(exp>max) max=exp;
if(exp<min)min=exp;
}
}
// cout<<max<<" "<<min<<endl;
for(i=max;i>=min;i--){
if(coe[i]!=0) num++;
}
cout<<num;//不能在这里加“ ”
for(i=max;i>=min;i--){
if(coe[i]!=0){
//cout<<i<<" "<<fixed<<setprecision(1)<<coe[i];
printf(" %d %.1f",i,coe[i]);
//if(i>min) cout<<" ";
}
}
return 0;
}
语法:(1)cout控制输出小数保留位数,cout<<i<<" "<<fixed<<setprecision(1)<<coe[i];记得添加头文件,或者直接用printf更方便。
6. 世界杯赔率(1011)
这题比较简单,就是一个3阶的二维数组,找出每行的最大值即可。按顺序读入,顺便记录下每行最大坐标即可,最后计算收益。
#include<iostream>
using namespace std;
int main(){
double odds[3][3];
int maxs[3]={0};//存储每局比赛的最大几率对应下标
int i,j;
double profit=0.65;
for(i=0;i<3;i++){
for(j=0;j<3;j++){
cin>>odds[i][j];
if(odds[i][j]>odds[i][maxs[i]]) maxs[i]=j;
}
}
for(i=0;i<3;i++){
if(maxs[i]==0) cout<<"W ";
else if(maxs[i]==1)cout<<"T ";
else cout<<"L ";
profit*=odds[i][maxs[i]];
}
profit=(profit-1)*2;
printf("%.2f",profit);
return 0;
}
7. 签到和签退(1006)
这题和之前做过的学生程序排序类似,思路都是先定义一个结构体存储每个人的ID和签到、签退时间信息。之后根据时间先后对结构体数组排序,找到最小签到和最大签退时间对应信息输出即可。
不过这题其实不需要排序,因为只涉及到找到两个最值,遍历一遍即可,可以进一步减少时间复杂度。
排序方法:
#include<iostream>
#include<algorithm>
using namespace std;
#define N 100
typedef struct sign{
string ID;
int SignIn[3],SignOut[3];
};
bool EarilTime(sign x,sign y){
if(x.SignIn[0]!=y.SignIn[0]) return x.SignIn[0]<y.SignIn[0];
else{
if(x.SignIn[1]!=y.SignIn[1]) return x.SignIn[1]<y.SignIn[1];
else return x.SignIn[2]<y.SignIn[2];
}
}
bool LastTime(sign x,sign y){
if(x.SignOut[0]!=y.SignOut[0]) return x.SignOut[0]>y.SignOut[0];
else{
if(x.SignOut[1]!=y.SignOut[1]) return x.SignOut[1]>y.SignOut[1];
else return x.SignOut[2] > y.SignOut[2];
}
}
int main(){
int n,i;
cin>>n;
sign s[N];
for(i=0;i<n;i++){
cin>>s[i].ID;
scanf("%d:%d:%d",&s[i].SignIn[0],&s[i].SignIn[1],&s[i].SignIn[2]);
scanf("%d:%d:%d",&s[i].SignOut[0],&s[i].SignOut[1],&s[i].SignOut[2]);
}
sort(s,s+n,EarilTime);
cout<<s[0].ID<<" ";
sort(s,s+n,LastTime);
cout<<s[0].ID;
return 0;
}
遍历找最值:(把时间转化成秒,就可以直接比较大小)
#include <iostream>
#include <climits>
using namespace std;
int main() {
int n, minn = INT_MAX, maxn = INT_MIN;
scanf("%d", &n);
string unlocked, locked;
for(int i = 0; i < n; i++) {
string t;
cin >> t;
int h1, m1, s1, h2, m2, s2;
scanf("%d:%d:%d %d:%d:%d", &h1, &m1, &s1, &h2, &m2, &s2);
int tempIn = h1 * 3600 + m1 * 60 + s1;
int tempOut = h2 * 3600 + m2 * 60 + s2;
if (tempIn < minn) {
minn = tempIn;
unlocked = t;
}
if (tempOut > maxn) {
maxn = tempOut;
locked = t;
}
}
cout << unlocked << " " << locked;
return 0;
}
8. 男生VS女生(1036)
这题和上面签到签退的题非常类似。都是用结构体存储个人信息,在读入时进行比较判断,保留当前分数最低的男生和分数最高的女生信息,最后输出即可。
#include<iostream>
using namespace std;
struct Student{
char name[11],ID[11];
char gender;
int grade;
}tem, male, female;
bool cmp(Student x,Student y){//x成绩小于y返回true
return x.grade<y.grade;//题目保证成绩均不同
}
int main(){
int n,i;
male.grade=101;
female.grade=-1;
cin>>n;
for(i=0;i<n;i++){
cin>>tem.name>>tem.gender>>tem.ID>>tem.grade;
//比较出男生最低成绩和女生最低成绩
if(tem.gender=='M') {
//tem.gender=="M"是错的,"M"表示一个字符串,而'M'才是字符(实际上是一个整型数)
if(cmp(tem,male)==true) male=tem;
}
else{
if(cmp(tem,female)==false) female=tem;
}
}
if(female.grade!=-1) cout<<female.name<<" "<<female.ID<<endl;
else cout<<"Absent"<<endl;
if(male.grade!=101) cout<<male.name<<" "<<male.ID<<endl;
else cout<<"Absent"<<endl;
if(female.grade!=-1&&male.grade!=101) cout<<female.grade-male.grade;
else cout<<"NA";
return 0;
}
语法:tem.gender=="M"是错的,"M"表示一个字符串,而'M'才是字符(实际上是一个整型数),才能与gender这个字符进行比较。
9. U型打印(1031)
本题关键在于确定n1和n2的值,根据和n1=n3<=n2可以推导出:n2=(N+2)/3+(N+2)%3,n1=(N+2)/3。剩下就可以按行输出了。
#include<iostream>
#include<string>
using namespace std;
int main(){
string s;
cin>>s;
int len=s.length()+2;
int n2=len/3+len%3,n1=len/3;
//cout<<n1<<endl;
//输出U的前面n1-1行
for(int i=0;i<n1-1;i++){
cout<<s[i];
for(int j=0;j<n2-2;j++){
cout<<" ";
}
cout<<s[len-3-i]<<endl;
}
for(int i=0;i<n2;i++){
cout<<s[n1-1+i];
}
return 0;
}
10. 回文数判断(1019)
本题分成两部分:(1)完成进制转换,只需不断对b取余和除以b,直到为0即可;
(2)判断回文数,比较头尾对应位置是否相同,一旦不同就不是回文数。
#include<iostream>
using namespace std;
int main(){
int N,b,num=0;
cin>>N>>b;
int bit[100];//逆序存储进制转化后的各位的数字
while(N!=0){
bit[num]=N%b;
N=N/b;
num++;
}
//num此时存储进制转化后的位数
int flag=1;
for(int i=0;i<num/2;i++){
if(bit[i]!=bit[num-1-i]) {
cout<<"No"<<endl;
flag=0;
break;
}
}
if(flag==1)cout<<"Yes"<<endl;
for(int i=num-1;i>=0;i--){//逆序输出时为i--,否则发生段错误
cout<<bit[i];
if(i!=0)cout<<" ";
}
return 0;
}
11. 货币相加(1058)
参考时间相加和进制转化的相关思想,先将货币全部转化成以最小的Knut为单位进行相加,再类似于进制转化,不断模和除以相应的转化大小即可。注意:要与有较大的测试用例,题目中说Gallon最大可到10^7,再转化为Knut后大小为27*19*10^7的大小,所以应该用long long型存储。
#include<iostream>
using namespace std;
int main(){
long long Gal,Sick,Knut;
long long sum=0;
for(int i=0;i<2;i++){
scanf("%lld.%lld.%lld",&Gal,&Sick,&Knut);
sum+=Gal*29*17+Sick*29+Knut;
}
Knut=sum%29;
sum/=29;
Sick=sum%17;
sum/=17;
Gal=sum;
cout<<Gal<<"."<<Sick<<"."<<Knut;
return 0;
}
语法:若用scanf读入,注意long long型的读入占位符为%lld。
12. 福尔摩斯的约会(1061)
本题是遍历4个字符串,比较找到相同元素,难度不大。注意以下几点:
(1)星期判断,直接写1-7天的if或switch判断太麻烦了,可以用week字符串数组先预存周一到到日对应的字符缩写;
(2)输出注意:时间都要写成两位的形式,比如七点零九分,得写成07:09的形式;
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdio>
using namespace std;
int mins(int a,int b){
return a<b?a:b;
}
int main(){
string d1,d2,s1,s2;
cin>>d1>>d2>>s1>>s2;
char week[7][4]={"MON","TUE","WED","THU","FRI","SAT","SUN"};
int i;
int len1=mins(d1.length(),d2.length()),len2=mins(s1.length(),s2.length());
for(i=0;i<len1;i++){
if(d1[i]==d2[i]&&d1[i]>='A'&&d1[i]<='G'){
cout<<week[d1[i]-'A']<<" ";
break;
}
}
for(int j=i+1;j<len1;j++){
if(d1[j]==d2[j]&&d1[j]>='0'&&d1[j]<='9'){
cout<<0<<d1[j]<<":";//时间都要占满两位数
break;
}
if(d1[j]==d2[j]&&d1[j]>='A'&&d1[j]<='N'){
cout<<10+d1[j]-'A'<<":";
break;
}
}
for(int j=0;j<len2;j++){
if(s1[j]==s2[j]&&((s1[j]>='a'&&s1[j]<='z')||(s1[j]>='A'&&s1[j]<='Z'))){
//if(j<10)cout<<0<<j;
//else cout<<j;
printf("%02d",j);
break;
}
}
return 0;
}
语法:(1)关于字符串存储,也可以用char s[70]这样的形式,读入方式为gets(s),长度为strlen(s);
(2)字符串数组week的说明,week[7][4],第二个维度至少是4,因为每个字符串还有‘\0’的结束标志占一位,输出第i行字符串,直接使用week[i]即可;
(3)打印输出:int保证强制两位输出,可用printf("%02d",j),这样就解决了0-9按两位输出的问题。
13. 科学计数法(1073)
本题主要是输出的情况比较多,要注意讨论,核心在于确定E的位置,确定E的大小,然后分为以下3种情况:
指数为正数,小数点右移:
(1)右移位数超过当前小数位数,需要在末尾补0,不需要输出小数点,如1.2E+10;
(2)右移位数少于当前小数位数,需要输出小数点,不需补0,如1.2345E+03;
指数为负数,小数点左移
(3)根据左移位数,在前面补上相应的零,要输出小数点。
其实对于指数位为0的特殊情况,也要单独考虑,但是测试用例没有考察这一情况。
#include<iostream>
#include<string>
using namespace std;
int main(){
string s;
cin>>s;
if(s[0]=='-') cout<<'-';
int e=0;//指示E的位置
int flag=0;//确定指数位的符号
int exp=0;//保存指数位大小
while(s[e]!='E'){
e++;
}
if(s[e+1]=='-') flag=1;
for(int i=e+2;i<s.length();i++){
exp=exp*10+s[i]-'0';
}
//cout<<exp<<endl;
if(exp==0) {//指数为0的特殊情况
for(int i=1;i<e;i++){
cout<<s[i];
}
return 0;
}
if(flag==0){//指数为正时的输出
int d=exp-e+3;
if(d>=0){//此时不需输入小数点
for(int i=1;i<e;i++){
if(s[i]!='.')cout<<s[i];
}
for(int i=0;i<d;i++) cout<<0;
}
else{//此时需要输出小数点
for(int i=1;i<e;i++){
if(s[i]!='.')cout<<s[i];
if(i==exp+2)cout<<'.';
}
}
}
else{//左移前面加0输出
cout<<"0.";
for(int i=1;i<exp;i++) cout<<0;
for(int i=1;i<e;i++){
if(s[i]!='.')cout<<s[i];
}
}
return 0;
}
14. 正确拼写(1005)
本题思路很简单,只要读入输入的数字(由于位数较多,应当用字符串读入),累加各个位数之和得到结果,逐位拼写即可(可以把结果再转化为字符串便于读取每一位)。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
char N[102];
cin.getline(N,102);//pat不再支持gets(N)的用法,
int len=strlen(N);//字符串长度不包括末尾的\0
char num[10][10]={"zero","one","two","three","four","five","six","seven","eight","nine"};
int sum=0;
for(int i=0;i<len;i++){
sum+=N[i]-'0';
}
string s=to_string(sum);
for(int i=0;i<s.length();i++){
cout<<num[s[i]-'0'];//这里的s[i]是字符数字,必须减去‘0’才能得到对应的数字
if(i<s.length()-1) cout<<" ";
}
return 0;
}
语法:(1)关于字符串读入建议还是使用string直接cin,pat已经不再支持gets()的读入方法,或者可以用cin.getline(s,长度)的用法。strlen()要添加头文件<cstring>
(2) 注意字符数字转化为int类型时不再对应0-9,而是相应的acill码,否则很可能越界出错
15. 密码改正(1035)
本题其实思路比较简单,遍历每条密码,修改对应的字符即可。其实如果不要求先输出修改密码个数的话,只需要两个字符串变量即可,改完即可输出。但是题目要求先输出修改个数,只能定义结构体数组先存储所有结果,统计完之后再输出。定义一个标志变量,表示字符串是否被修改了。
#include<iostream>
using namespace std;
struct Account{
string ID,key;
};
int main(){
int n;
cin>>n;
Account acc[1002];
int order[1002];
int num=0;//统计修改个数
for(int i=0;i<n;i++){
cin>>acc[i].ID>>acc[i].key;
int flag=0;
for(int j=0;j<acc[i].key.length();j++){
if(acc[i].key[j]=='1') {
acc[i].key[j]='@';flag=1;
}
else if(acc[i].key[j]=='0') {
acc[i].key[j]='%';flag=1;
}
else if(acc[i].key[j]=='l') {
acc[i].key[j]='L';flag=1;
}
else if(acc[i].key[j]=='O') {
acc[i].key[j]='o'; flag=1;
}
}
if(flag==1) {//已修改密码的记录下其序号,并把个数+1
order[num]=i;
num++;
}
}
if(num!=0){
cout<<num<<endl;
for(int i=0;i<num;i++){
cout<<acc[order[i]].ID<<" "<<acc[order[i]].key<<endl;
}
}
else{
if(n==1)
cout<<"There is 1 account and no account is modified";
else cout<<"There are "<<n<<" accounts and no account is modified";
}
return 0;
}
参考解答:除结构体之外,还可以用vector存储多条字符串
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
scanf("%d", &n);
vector<string> v;
for(int i = 0; i < n; i++) {
string name, s;
cin >> name >> s;
int len = s.length(), flag = 0;
for(int j = 0; j < len; j++) {
switch(s[j]) {
case '1' : s[j] = '@'; flag = 1; break;
case '0' : s[j] = '%'; flag = 1; break;
case 'l' : s[j] = 'L'; flag = 1; break;
case 'O' : s[j] = 'o'; flag = 1; break;
}
}
if(flag) {
string temp = name + " " + s;
v.push_back(temp);
}
}
int cnt = v.size();
if(cnt != 0) {
printf("%d\n", cnt);
for(int i = 0; i < cnt; i++)
cout << v[i] << endl;
} else if(n == 1) {
printf("There is 1 account and no account is modified");
} else {
printf("There are %d accounts and no account is modified", n);
}
return 0;
}
16. 找公共后缀(1077)
思路是逐个读入字符串,与第一条字符串比较末尾序列,找出这些公共末尾序列长度的最小值,一旦出现为0的情况,则没有公共后缀,直接输出“nai”后返回;否则根据后缀长度输出末尾序列即可。
#include<iostream>
#include<cstring>
using namespace std;
int main(){
int N;
cin>>N;
cin.get();//读入回车,保证后面读入字符串不出错
char temp[257];
//char tail[257];
cin.getline(temp,257);
int sublen=strlen(temp);//存储最短公共子序列长度
for(int i=0;i<N-1;i++){
char str[257];
cin.getline(str,257);
int t=strlen(temp);
int s=strlen(str);
int num=0;
while(temp[t-1]==str[s-1]&&t>0&&s>0&&num<=sublen){
//tail[num]=str[s-1];
num++;//统计公共尾序列长度
t--;
s--;
}
if(num==0){
cout<<"nai";
return 0;
}
else{
if(num<sublen) sublen=num;
}
}
int len=strlen(temp);
for(int i=0;i<sublen;i++){
cout<<temp[len-sublen+i];
}
return 0;
}
17. 用中文读数字(1082)
这题我没有正确做出来,主要感觉处理起来太繁琐了,要根据序列长度情况决定什么时候输出“qian”,"bai","shi"等,还要考虑中间为0的情况。
在参考网络解答后,解决方法如下:(1)根据输出规则,4位数划分为1节,按节处理,分别用left\right指示各节的左右;
(2)根据输出的是当前节的第几位,决定在数字后加上千百十,个位不需输出;
(3)各节输出完成后,要考虑输出万或者亿,根据节数来输出,从右数第三节末尾输出亿,第2节末尾输出万;
(4)中间位为0的情况,用一个标志位flag处理。
#include<iostream>
using namespace std;
int main(){
char num[10][10]={"ling","yi","er","san","si","wu","liu","qi","ba","jiu"};
char wei[5][5]={"Shi","Bai","Qian","Wan","Yi"};
string s;
cin>>s;
//cout<<s<<endl;
int len=s.length();
int left=0,right=len-1;//left和right分别指示字符串的头尾位置
if(s[0]=='-'){
cout<<"Fu";
left++;//指示到第一位数字
}
while(left+4<=right){
right-=4;//控制left和right指示到同一节的首尾
}
while(left<len){//从左到右处理数字中的某一节
bool flag=false;//标记是否有累计的0
bool isPrint=false;//表示该节没有输出过其中的位
while(left<=right){//处理这一节的输出
if(left>0&&s[left]=='0'){
flag=true;
}
else{
if(flag==true){//如果存在累积的0
cout<<" ling";
flag=false;
}
if(left>0) cout<<" ";//非首位的话,前面要输出空格
cout<<num[s[left]-'0'];//输出当前数字
isPrint=true;//该节已有数字输出
if(left!=right){//除了个位以外,其它位需要输出十百千
//right指示着本节的个位,left从头部右移
cout<<" "<<wei[right-left-1];
}
}
left++;
}
if(isPrint==true&&right!=len-1){//只要不是最后一节,就要输出万或亿
//根据所在节数(len-1-right)输出,从右数第3节输出亿,第2节输出万
//isPrint==true是避免1,0000,1111这种第2节全为0的数输出万
cout<<" "<<wei[(len-1-right)/4+2];
}
right+=4;//进入下一节
}
return 0;
}
18. 德才论(A1062)
这一题关键在于先根据德行和才能得分分类,再分别对其排序,但按这条思路就需要定义4个数组进行存储,还要再读入后复制到对应的组内,这样处理起来非常繁琐,而且还容易爆栈(当然,这应该是因为我把大型数组定义再来main函数内)。
参考网络解答,我们可以给结构体增加一个类别属性,既然输出是按从圣人到愚人的顺序,那我们在定义排序函数时,可以依次按类别、总分、德行、序号排序,这样只需一个数组处理,空间也更少。
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
struct Man {
char ID[20];
int Virtue, Talent,flag;//flag表示所属的类
}man[100010];
bool cmp(Man x, Man y) {
int Xsum = x.Virtue + x.Talent, Ysum = y.Virtue + y.Talent;
if(x.flag!=y.flag) return x.flag < y.flag;//先排序号小的类
else if (Xsum != Ysum) return Xsum > Ysum;
else if(x.Virtue != y.Virtue) return x.Virtue > y.Virtue;
else return strcmp(x.ID, y.ID) < 0;
}
int main() {
int n, L, H;
cin >> n >> L >> H;
int m=n;
for (int i = 0; i < n; i++) {
cin >> man[i].ID >> man[i].Virtue >>man[i].Talent;
if (man[i].Virtue <L || man[i].Talent<L) {
m--;
man[i].flag=5;
//continue;
}
else if (man[i].Virtue >= H && man[i].Talent >= H) {
man[i].flag=1;
}
else if (man[i].Virtue >= H && man[i].Talent < H) {
man[i].flag=2;
}
else if (man[i].Virtue >= man[i].Talent && man[i].Virtue < H && man[i].Talent < H) {
man[i].flag=3;
}
else {
man[i].flag=4;
}
}
sort(man, man + n, cmp);
cout<<m<<endl;
for(int i=0;i<m;i++){
cout<<man[i].ID<<" "<<man[i].Virtue<<" "<<man[i].Talent<<endl;
//(i<m-1)cout<<endl;
}
return 0;
}
语法:复习strcmp(s1,s2)函数,s1<s2返回-1,s1=s2返回0,s1>s2返回1。
19. 最佳排序(A1012)
本题的难点在于:(1)如何根据学生ID查询到其对应的成绩;(2)如何取得4个排名并进行输出。
参考网上解答后,注意到ID全是6位数字,所以可以直接用Rank[1000000][4]存储ID对应的4个排名,并考虑到4个排名的优先级,按顺序存储A\C\M\E。4个排名则需要对成绩作4次排序,每次排序后存储排名(注意同分的排名应该相同)
#include<iostream>
#include<algorithm>
using namespace std;
int t = 0;
int Rank[1000000][4] = { 0 };//存储排名
struct Students {
int ID;
int grade[4];//依次存储A/C/M/E成绩
}stu[2010];
bool cmp(Students x, Students y) {
return x.grade[t] > y.grade[t];
}
int main() {
int N, M;
cin >> N >> M;
for (int i = 0; i < N; i++) {
cin >> stu[i].ID >> stu[i].grade[1] >> stu[i].grade[2] >> stu[i].grade[3];
stu[i].grade[0] = (stu[i].grade[1] + stu[i].grade[2] + stu[i].grade[3]) / 3;
}
for (t = 0; t < 4; t++) {
sort(stu, stu + N, cmp);
Rank[stu[0].ID][t] = 1;
for (int i = 1; i < N; i++) {
if (stu[i - 1].grade[t] == stu[i].grade[t]) {
//若分数相等,则排名应该相同
Rank[stu[i].ID][t] = Rank[stu[i - 1].ID][t];
}
else {
//否则排名应该为其数组下标+1
Rank[stu[i].ID][t] = i + 1;
}
}
}
int num;
char subject[5] = { 'A','C','M','E' };
for (int i = 0; i < M; i++) {
cin >> num;
if (Rank[num][0] == 0) cout << "N/A" << endl;
else {
int tem = 0;
for (int j = 1; j < 4; j++) {
if (Rank[num][j] < Rank[num][tem]) tem = j;
}
cout << Rank[num][tem] << " " << subject[tem] << endl;
}
}
return 0;
}
语法:(1)若题目要求计算4舍5入后的结果,则可以用
stu[i].grade[0] =round( (stu[i].grade[1] + stu[i].grade[2] + stu[i].grade[3]) / 3)+0.5;
round保留四舍5入后的结果
(2)Rank数组不可以命名为rank,会与关键字发生重复;
(3)Rank这种很大的数组应该定义在main函数外部,否则会发生段错误。因为全局变量在静态存储区内分配内存,而局部变量是在栈内分配内存空间的。C语言编写的程序会在运行期间创建一个栈堆段,用来保存函数的调用关系和局部变量。而在main函数内部定义大数组相当于在栈内需要一个很大的空间,会造成栈的溢出。
因此,当我们需要定义一个极大的数组时,最好在mian 函数外部定义这个大数组。
20. 电话账单(A1016)
这题相当繁琐,难点主要在于:
(1)正确匹配用户的每次通话记录。通过设置flag表示on/off-line状态,以及排序可以绑定一次通话的两条记录在相邻位置上;
(2)计算每次通话的电话费,以及总费用。算钱的难点在于,由于每个小时的费用都不全相同,所以不能简单的求出时间差×单价,我是写了一个计算单日内任意两时刻内的费用的函数,若跨天,则分情况调用。当然也可以从开始时间逐分钟累加到结束时间,同时计费。
(3)有的用户没有正确的通话账单不需要输出,有的用户有多次消费记录,要正确按照格式输出。设置tem作为输出标志,用于给有多次消费的用户在正确时刻输出总账。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int prices[24];
struct Customer{
char name[30];
int mon,day,hour,min;
int flag;//0表示online,1表示offline
}cust[1010];
bool cmp(Customer x,Customer y){
if(strcmp(x.name,y.name)!=0) return strcmp(x.name,y.name)<0;
else if(x.mon!=y.mon) return x.mon<y.mon;
else if(x.day!=y.day) return x.day<y.day;
else if(x.hour!=y.hour) return x.hour<y.hour;
else return x.min<y.min;
}
double amount(int h1,int m1,int h2,int m2){
//计算小时内的金额
double cost=0;
if(h1==h2){
cost = (m2-m1)*prices[h1];
}
else{
cost=(60-m1)*prices[h1];
for(int j=h1+1;j<h2;j++){
cost += prices[j]*60;
}
cost+=m2*prices[h2];
}
return cost/100.;
}
int main(){
for(int i=0;i<24;i++){
scanf("%d",&prices[i]);
}
int n;
scanf("%d",&n);
char str[30];
for(int i=0;i<n;i++){
scanf("%s %d:%d:%d:%d %s",cust[i].name,&cust[i].mon,&cust[i].day,&cust[i].hour,&cust[i].min,str);
if(strcmp(str,"on-line")==0) {
cust[i].flag=0;
//printf("%d",cust[i].flag);
}
else cust[i].flag=1;
}
sort(cust,cust+n,cmp);
int tem=0;//用作每个用户开始输出标志
//int out=0;
double sum=0;
for(int i=0;i<n-1;i++){
if(strcmp(cust[i].name,cust[i+1].name)==0){
if(cust[i].flag==0 && cust[i+1].flag==1){
double cost=0;
//是否有不同月份
if(tem==0) {
printf("%s %02d\n",cust[i].name,cust[i].mon);
//out=1;
tem=1;
}
printf("%02d:%02d:%02d %02d:%02d:%02d",cust[i].day,cust[i].hour,cust[i].min,cust[i+1].day,cust[i+1].hour,cust[i+1].min);
int time =cust[i+1].day*1440+cust[i+1].hour*60+cust[i+1].min - (cust[i].day*1440+cust[i].hour*60+cust[i].min);
if(cust[i+1].day!=cust[i].day){//跨天计费
cost =amount(cust[i].hour,cust[i].min,23,59)+prices[23]/100.;
cost +=(amount(0,0,23,59)+prices[23]/100.)*(cust[i+1].day-cust[i].day-1);
cost +=amount(0,0,cust[i+1].hour,cust[i+1].min);
}
else{
cost= amount(cust[i].hour,cust[i].min,cust[i+1].hour,cust[i+1].min);
}
printf(" %d $%.2f\n",time,cost);
sum+=cost;
}
}
else {//切换到下一个用户时重置标记
if(tem==1) printf("Total amount: $%.2f\n",sum);
tem=0;
sum=0;
}
}
//如果tem=1,.说明最后一个用户存在有效的账单,他还没有输出Total amount
if(tem==1)printf("Total amount: $%.2f\n",sum);
return 0;
}
语法:(1)注意审题,开始没注意到用户名最长为20字符的要求,只设置了10个数组长,导致后3个测试点卡了很久o(╥﹏╥)o;
(2)控制输出格式:要控制整型的输出位数,使用%md,m表示输出位数,不足在前面补0,否则正常输出。
21. PAT排名(A1025)
这题难度不大,关键在实现本地排名和总排名。在完成一个考试地点的信息读入后,就可以对这一块数据进行排序了,得到本地排名。全部地点读完后,再进行总排名,最后根据总排名输出即可。要小心的地方是本地排名时sort的范围。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Testee{
char ID[15];
int grade,location;
int FinRank,LocRank;
}T[30010];
bool cmp(Testee x,Testee y){
if(x.grade!=y.grade) return x.grade > y.grade;
else return strcmp(x.ID,y.ID)<0;
}
int main(){
int n;
cin>>n;
int k,num=0;
for(int i=0;i<n;i++){
cin>>k;
for(int j=0;j<k;j++){
cin>>T[num].ID>>T[num].grade;
T[num].location = i+1;
num++;
}
//sort(start,end,cmp),start是起始位置,end是最后一位+1
sort(T+num-k,T+num,cmp);
T[num-k].LocRank=1;
for(int j=num-k+1;j<num;j++){
if(T[j].grade==T[j-1].grade) T[j].LocRank =T[j-1].LocRank;
else T[j].LocRank = j-(num-k)+1;
}
}
sort(T,T+num,cmp);
T[0].FinRank=1;
cout<<num<<endl;
cout<<T[0].ID<<" "<<T[0].FinRank<<" "<<T[0].location<<" "<<T[0].LocRank<<endl;
for(int j=1;j<num;j++){
if(T[j].grade==T[j-1].grade) T[j].FinRank =T[j-1].FinRank;
else T[j].FinRank = j+1;
cout<<T[j].ID<<" "<<T[j].FinRank<<" "<<T[j].location<<" "<<T[j].LocRank<<endl;
}
return 0;
}
语法:sort(start,end,cmp),start是起始位置,end是最后一位+1
22. 列表排序(1028)
这题比较简单,写3个cmp函数,根据C值选用即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct Students{
int ID,grade;
char name[10];
}stu[100010];
bool cmp1(Students x,Students y){
return x.ID<y.ID;
}
bool cmp2(Students x,Students y){
if(strcmp(x.name,y.name)!=0) return strcmp(x.name,y.name)<0;
else return x.ID<y.ID;
}
bool cmp3(Students x,Students y){
if(x.grade!=y.grade) return x.grade<y.grade;
else return x.ID<y.ID;
}
int main(){
int n,c;
cin>>n>>c;
for(int i=0;i<n;i++){
cin>>stu[i].ID>>stu[i].name>>stu[i].grade;
}
if(c==1){
sort(stu,stu+n,cmp1);
}
else if(c==2){
sort(stu,stu+n,cmp2);
}
else{
sort(stu,stu+n,cmp3);
}
for(int i=0;i<n;i++){
//cout<<stu[i].ID<<" "<<stu[i].name<<" "<<stu[i].grade<<endl;
printf("%06d %s %d\n",stu[i].ID,stu[i].name,stu[i].grade);
}
return 0;
}
23. 富豪排名(1055)
这题我开始的想法是先对所有人按年龄排名,每次查询时先确定对应的年龄范围内的所有人,再对这部分人按财富、年龄、姓名排序,最后进行输出。但这样会导致:每次查询后都破坏原先的有序年龄序列,因此需要复原(重排序或者借用辅助数组),过多的排序导致时间复杂度过大,在第二个测试点会超时。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Man {
char name[10];
int age, wealth;
}stu[1010];
bool CmpAge(Man x, Man y) {
//先按年龄对数组做排序
return x.age < y.age;
}
bool CmpWealth(Man x, Man y) {
//按财富、年龄、姓名的优先级排序
if (x.wealth != y.wealth) return x.wealth > y.wealth;
else if (x.age != y.age) return x.age < y.age;
else return strcmp(x.name, y.name) < 0;
}
void AgeRange(int amin, int amax, int& low, int& high, int n) {
for (int i = 0; i < n; i++) {
if (stu[i].age >= amin) {
low = i;
break;
}
}
if (low == -1) return;
for (int i = n - 1; i >= low; i--) {
if (stu[i].age <= amax) {
high = i;
break;
}
}
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
int num[1010], Amin[1010], Amax[1010];
for (int i = 0; i < n; i++) {
scanf("%s%d%d", stu[i].name, &stu[i].age, &stu[i].wealth);
}
for (int i = 0; i < m; i++) {
scanf("%d%d%d", &num[i], &Amin[i], &Amax[i]);
}
sort(stu, stu + n, CmpAge);
int low = -1, high = -1;
for (int i = 0; i < m; i++) {
printf("Case #%d:\n", i + 1);
AgeRange(Amin[i], Amax[i], low, high, n);
printf("%d %d %d %d\n", low, high, Amin[i], Amax[i]);
if (low == -1 || high == -1) {
printf("None\n");
}
else {
sort(stu + low, stu + high + 1, CmpWealth);
int tem;
num[i] < (high - low + 1) ? tem = num[i] : tem = (high - low + 1);
for (int j = 0; j < tem; j++) {
printf("%s %d %d\n", stu[low + j].name, stu[low + j].age, stu[low + j].wealth);
}
}
low = -1;
high = -1;
sort(stu + low, stu + high + 1, CmpAge);
}
return 0;
}
参考网络解答后,要注意到每次查询最多只查询100人,因此,可以将原数组中同年龄的财富前100名存储到辅助数组中,每次查询时采用遍历搜索符合年龄段要求的,这样查询的时间复杂度可以降低为线性时间。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Man{
char name[10];
int age,wealth;
}stu[100010],valid[100010];
//valid存储所有人在各自年龄中财富值前100名内的人
int Age[210]={0};//存储各年龄的人数
bool CmpWealth(Man x,Man y){
//按财富、年龄、姓名的优先级排序
if(x.wealth!=y.wealth) return x.wealth>y.wealth;
else if(x.age!=y.age) return x.age<y.age;
else return strcmp(x.name,y.name)<0;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%s%d%d",stu[i].name,&stu[i].age,&stu[i].wealth);
}
sort(stu,stu+n,CmpWealth);
int validnum=0;//表示存放到valid数组中人数
for(int i=0;i<n;i++){
if(Age[stu[i].age]<100){
Age[stu[i].age]++;
valid[validnum++]=stu[i];
}
}
int num,amax,amin;
for(int i=0;i<m;i++){
scanf("%d%d%d",&num,&amin,&amax);
printf("Case #%d:\n",i+1);
int Psnum=0;//统计本次查询已经输出人数
for(int j=0;j<validnum&&Psnum<num;j++){
if(valid[j].age>=amin&&valid[j].age<=amax){
printf("%s %d %d\n", valid[j].name, valid[j].age, valid[j].wealth);
Psnum++;
}
}
if(Psnum==0) printf("None\n");
}
return 0;
}
语法:(1)相同结构体变量之间可以直接赋值;
(2)字符串间复制,可以使用strcpy(s1,s2),把s2复制给s1。
24. PAT判断(1075)
这题的注意点在于对第一次编译未通过的提交(分值显示为-1),但在记分时应该要修改为0,而从未提交的情况则按没有分数(输出“-”)处理。由于题目中说,全场没有提交和没有通过编译的提交的考生不显示,所以在读入时要注意设置符号变量判断。
当然这题我做复杂了。因为我开始以为输入的考生号是散乱的,不一定从1-N到都有,所以又定义了一个备份数组存储实际存在的考生信息,实际上根据题意1-N号考生都有信息,可以直接读入后排序。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Users{
int ID=0;
int sum=0;//sum统计总分
int grade[5]={-1,-1,-1,-1,-1};
int gradenum=0;//拿到满分的题目数量
}user[100010],valid[10010];
bool cmp(Users x,Users y){
if(x.sum!=y.sum) return x.sum>y.sum;
else if(x.gradenum!=y.gradenum) return x.gradenum>y.gradenum;
else return x.ID<y.ID;
}
int main(){
int n,k,m;
scanf("%d%d%d",&n,&k,&m);
int score[6]={0};
for(int i=0;i<k;i++){
scanf("%d",&score[i]);
}
int num,t,mark;
for(int i=0;i<m;i++){
scanf("%d%d%d",&num,&t,&mark);
if(mark>user[num].grade[t-1]){
//更新该题分数
//开始时,mark>-1表示有能耐通过编译的提交
user[num].ID=1;//ID这里用作指示器,变为1表示这名学生存在有效成绩
user[num].grade[t-1]=mark;
if(user[num].grade[t-1]==score[t-1]) user[num].gradenum++;
}
if(mark==-1 && user[num].grade[t-1]==-1){
//第一次编译错误后将分值修改为0分
user[num].grade[t-1]=0;
}
}
int j=0;
for(int i=0;i<100010;i++){
if(user[i].ID==1) {
valid[j]=user[i];
valid[j].ID=i;
for(int x=0;x<k;x++){
//printf(" %d",valid[j].grade[x]);
if(valid[j].grade[x]>=0) valid[j].sum+=valid[j].grade[x];
}
j++;
// printf("\n");
}
}
//printf("j=%d\n",j);
sort(valid,valid+j,cmp);
//第一名单独输出
int rank=1;
printf("%d",rank);
printf(" %05d %d",valid[0].ID,valid[0].sum);
for(int x=0;x<k;x++){
if(valid[0].grade[x]>=0) printf(" %d",valid[0].grade[x]);
else printf(" -");
}
printf("\n");
for(int i=1;i<j;i++){
if(valid[i-1].sum==valid[i].sum) {
printf("%d",rank);
}
else {
printf("%d",i+1);
rank=i+1;
}
printf(" %05d %d",valid[i].ID,valid[i].sum);
for(int x=0;x<k;x++){
if(valid[i].grade[x]>=0) printf(" %d",valid[i].grade[x]);
else printf(" -");
}
printf("\n");
}
return 0;
}
修改后代码如下:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
struct Users{
int flag=0,ID;
int sum=0;//sum统计总分
int grade[5];
int gradenum=0;//拿到满分的题目数量
}user[10010];
void init(int n){//初始化
for(int i=1;i<=n;i++){
user[i].ID=i;
memset(user[i].grade,-1,sizeof(user[i].grade));
}
}
bool cmp(Users x,Users y){
if(x.flag!=y.flag) return x.flag>y.flag;
else if(x.sum!=y.sum) return x.sum>y.sum;
else if(x.gradenum!=y.gradenum) return x.gradenum>y.gradenum;
else return x.ID<y.ID;
}
int main(){
int n,k,m;
scanf("%d%d%d",&n,&k,&m);
init(n);
int score[6]={0};
for(int i=0;i<k;i++){
scanf("%d",&score[i]);
}
int num,t,mark;
for(int i=0;i<m;i++){
scanf("%d%d%d",&num,&t,&mark);
if(mark>user[num].grade[t-1]){
//更新该题分数
//开始时,mark>-1表示有能耐通过编译的提交
user[num].flag=1;//ID这里用作指示器,变为1表示这名学生存在有效成绩
user[num].grade[t-1]=mark;
if(user[num].grade[t-1]==score[t-1]) user[num].gradenum++;
}
if(mark==-1 && user[num].grade[t-1]==-1){
//第一次编译错误后将分值修改为0分
user[num].grade[t-1]=0;
}
}
for(int i=1;i<=n;i++){
for(int x=0;x<k;x++){
//printf(" %d",valid[j].grade[x]);
if(user[i].grade[x]>=0) user[i].sum+=user[i].grade[x];
}
}
//printf("j=%d\n",j);
sort(user+1,user+n+1,cmp);
//第一名单独输出
int rank=1;
printf("%d",rank);
printf(" %05d %d",user[1].ID,user[1].sum);
for(int x=0;x<k;x++){
if(user[1].grade[x]>=0) printf(" %d",user[1].grade[x]);
else printf(" -");
}
printf("\n");
for(int i=2;i<=n;i++){
if(user[i].flag==0) break;
if(user[i-1].sum==user[i].sum) {
printf("%d",rank);
}
else {
printf("%d",i);
rank=i;
}
printf(" %05d %d",user[i].ID,user[i].sum);
for(int x=0;x<k;x++){
if(user[i].grade[x]>=0) printf(" %d",user[i].grade[x]);
else printf(" -");
}
printf("\n");
}
return 0;
}
语法:(1)int a[5]={-1};这样的初始法只把第一个元素初始化为-1,其余元素都默认初始化为0,可以用memset吧整个数组值变为-1或0.(不可赋值为1,因为memset是按字节赋值)
(2) 对于复杂的结构体初始化,可以选择定义构造函数。
25. 成绩列表(1083)
简单题,直接按成绩降序排列,在遍历查找指定成绩范围的信息输出即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Student{
char name[12],ID[12];
int grade;
}stu[110];
bool cmp(Student x, Student y){
return x.grade>y.grade;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s%s%d",stu[i].name,stu[i].ID,&stu[i].grade);
}
int min,max;
scanf("%d%d",&min,&max);
sort(stu,stu+n,cmp);
int flag=0;
for(int i=0;i<n;i++){
if(stu[i].grade<=max && stu[i].grade>=min){
printf("%s %s\n",stu[i].name,stu[i].ID);
flag=1;
}
if(stu[i].grade<min) break;
}
if(flag==0) printf("NONE\n");
return 0;
}