文章目录
ICPC训练联盟2021寒假冬令营(1)
A - Specialized Four-Digit Numbers
题目
找到并列出所有具有这样特性的十进制的4位数字:其4位
数字的和等于这个数字以16进制表示时的4位数字的和,也
等于这个数字以12进制表示时的4位数字的和。
• 例如,整数2991的(十进制)4位数字之和是 2+9+9+1 = 21,
因为 2991 = 11728 + 8144 + 9*12 + 3,所以其12进制表示
为189312,4位数字之和也是21。但是2991的十六进制表示
为BAF16,并且11+10+15 = 36,因此2991被程序排除了。
• 下一个数是2992,3种表示的各位数字之和都是22 (包括
BB016),因此2992要被列在输出中。(本题不考虑少于4位
数字的十进制数----排除了前导零----因此2992是第一个正确
答案。)
• 输入
• 本题没有输入。
• 输出
• 输出为2992和所有比2922大的满足需求的4位数字(以严格的递
增序列),每个数字一行,数字前后不加空格,以行结束符结束。
输出没有空行。输出的前几行如下所示
2992
2993
2994
2995
2996
2997
2998
2999
...
分析
写一个函数,计算转换成n进制后的各位数字之和。
只需要写一个函数,不用每个进制转换都写一遍。
代码
#include<iostream>
using namespace std;
int fun(int a,int n)
{
int sum=0;
int t;
while(a)
{
t=a%n;
sum+=t;
a/=n;
}
return sum;
}
int main()
{
for(int i=2992;i<10000;i++)
{
if(fun(i,10)==fun(i,16)&&fun(i,10)==fun(i,12))
cout<<i<<endl;
}
return 0;
}
B - Pig-Latin
题目
您意识到PGP加密系统还不足够保护您的电子邮件,
所以,你决定在使用PGP加密系统之前,先把您的
明文字母转换成Pig Latin(一种隐语),以完善加
密。
• 输入和输出
• 请您编写一个程序,输入任意数量行的文本,并以Pig Latin输出。
每行文本将包含一个或多个单词。一个“单词”被定义为一个连
续的字母序列(大写字母和/或小写字母)。单词根据以下的规则
转换为Pig Latin,非单词的字符在输出时则和输入中出现的完全
一样:
• [1] 以元音字母(a、e、i、o或u,以及这些字母的大写形式)开
头的单词,要在其后面附加字符串“ay”(不包括双引号)。例
如,“apple”变成“appleay”。
• [2] 以辅音字母(不是A, a, E, e, I, i, O, o, U 或 u的任何字母)开头
的单词,要去掉第一个辅音字母,并将之附加在单词的末尾,然
后再在单词的末尾加上“ay”。例如,“hello”变成“ellohay”。
• [3] 不要改变任何字母的大小写
输入
This is the input.
输出
hisTay isay hetay inputay.
分析
元音字母单词后面直接加ay,辅以字母开头单词,把第一个辅音字母换到单词末尾再加ay,其他的不变
C++字符判断函数,cctype头文件中的函数
isalpha(a),如果a是字母,函数返回true;
isdigit(a),如果a是数字,函数返回true;
isalnum(a),如果a是字母或数字,函数返回true;
isupper(a),如果a是大写字母,函数返回true;
islower(a),如果a是小写字母,函数返回true;
tolower(a),如果a是大写字母,返回其小写,否则返回a
toupper(a),如果a是小写字母,返回其大写,否则返回a
代码
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
bool judge(char ch)//判断元音
{
ch=toupper(ch);
if(ch=='A'||ch=='E'||ch=='I'||ch=='O'||ch=='U')
return true;
return false;
}
int main()
{
string s;
while(getline(cin,s))//读入一行
{
int len=s.size();
for(int i=0;i<len;i++)//分析每一行
{
if(isalpha(s[i]))//如果是字母
{
//是元音字母
if(judge(s[i]))
{
while(isalpha(s[i]))
{
cout<<s[i];
i++;
}
cout<<"ay";
i--;
}
else//是辅音字母
{
char t=s[i];
i++;
while(isalpha(s[i]))
{
cout<<s[i];
i++;
}
cout<<t<<"ay";
i--;
}
}
else cout<<s[i];
}
cout<<endl;
}
}
C - Tic Tac Toe
题目
• 输入
• 输入的第一行给出N,表示测试用例的数目。然后给出
4N-1行,说明N个用空行分隔的网格图。
• 输出
• 对于每个测试用例,在一行中输出"yes"或"no",表示该
网格图是否是有效的三连棋游戏的一个步骤
Sample Input
2
X.O
OO.
XXX
O.X
XX.
OOO
Sample Output
yes
no
分析
分析多种情况。
因为玩家X先走,所以O数目小于等于X数目
X数目不会比O数目多2个
玩家O和X不能同时判赢
玩家O赢了,O的个数必须等于X个数
玩家X赢了,O的个数必须小于X个数
所以只要不满足上面五个条件之一,就是无效的
要写判断赢的函数,一行,一列,主对角线,副对角线逐个分析。
代码
#include<iostream>
#include<string>
#include<cctype>
using namespace std;
char plant[4][4];
int i,j;
bool win(char c)//判断谁赢的函数
{
for(i=0;i<3;i++)
{
for(j=0;j<3&&plant[i][j]==c;j++)//判断一行是否相同
if(j==2)
return true;
for(j=0;j<3&&plant[j][i]==c;j++)//判断一列是否相同
if(j==2)
return true;
}
for(i=0;i<3&&plant[i][i]==c;i++)//判断主对角线是否相同
if(i==2)
return true;
for(i=0;i<3&&plant[i][2-i]==c;i++)//判断副对角线是否相同
if(i==2)
return true;
return false;
}
int main()
{
int flag;
int n,xcount,ocount;
cin>>n;
getchar();
while(n--)
{
xcount=0;
ocount=0;
for(i=0;i<3;i++)//输入网格数据
cin>>plant[i];
flag=1;
for(i=0;i<3;i++)//计算x和O出现的次数
{
for(j=0;j<3;j++)
{
if(plant[i][j]=='X')
xcount++;
else if(plant[i][j]=='O')
ocount++;
}
}
if(win('X')&&win('O'))//两个人同时赢了
flag=0;
if(win('X')&&xcount==ocount)//X赢了,但双方棋子一样多
flag=0;
if(ocount>xcount||xcount-ocount>1)//O个数大于X或者x比O多于1
flag=0;
if(win('O')&&ocount!=xcount)//O赢了,但是双方棋子不等
flag=0;
if(win('X')&&ocount==xcount)//X赢,但是双方棋子个数相同
flag=0;
if(flag)
cout<<"yes"<<endl;
else
cout<<"no"<<endl;
}
return 0;
}
D - Factorial! You Must be Kidding!!!
题目
• 输入
• 输入包含若干行,每行给出一个整数n。不会有整数超过6位。输
入以EOF结束。
• 输出
• 对于每一行的输入,输出一行。如果n!的值在Arif 计算机的无符
号长整数范围内,输出行给出n!的值;否则输出行给出如下两行
之一:
•
Overflow!
//(当 n! > 6227020800)
•
Underflow!
//(当 n! < 10000)
Sample Input
2
10
100
Sample Output
Underflow!
3628800
Overflow!
分析
首先,离线计算F[i]=i!,8≤i≤13。
• 然后,对每个n,
• 如果8≤n≤13,则输出F[n];
• 如果(n>=14||(n<0&&(-n)%2==1)),则输出"Overflow!";
• 如果(n<=7 || (n<0&&(-n)%2==0)),则输出"Underflow!"。
• 在参考程序中,函数init()离线计算F[i]=i!,0≤i≤13。在
函数中,循环变量i为局部变量;而F[i]则为全局变量。
递归问题打表
递归容易栈溢出,用循环不用递归来打表,算一下题目要求的数字,不能直接用递归来做
代码
#include<iostream>
#include<string>
using namespace std;
typedef long long ll;
/*
打表,先离线计算F[i]=i!,8<=i<=13;
对每个n
8<=n<=13,输出F[n];
n>=14||(n<0&&(-n)%2==1),Overflow!
n<=7||(n<0&&(-n)%2==0),Underflow!
n=7时,n!=
*/
//递归函数
/*
int fac(int n)
{
if(n==0)
return 1;//递归边界
if(n>=1)
return fac(n)*fac(n-1);
}
*/
//n=13时,13!=6227020800
const int N=13;
const long long Min=10000,Max=6227020800;
long long F[N+1];//定义全局变量,用于打表
void init()
{
F[0]=1;
for(int i=1;i<=N;i++)
F[i]=i*F[i-1];
}
int main()
{
init();
int n;
while(cin>>n)
{
if(n>N||(n<0&&(-n)%2==1))
cout<<"Overflow!"<<endl;
else if(F[n]<Min||(n<0&&(-n)%2==0))
cout<<"Underflow!"<<endl;
else
cout<<F[n]<<endl;
}
return 0;
}
E - Function Run Fun
题目
我们都爱递归!不是吗?
• 请考虑一个三参数的递归函数w(a, b, c):
• if a <=0 or b <= 0 or c <=0, then w(a, b, c) 返回1;
• if a > 20 or b > 20 or c > 20, then w(a, b, c) 返回w(20, 20, 20);
• if a < b and b < c, then w(a, b, c) 返回w(a, b, c-1)+w(a, b-1, c-1)- w(a, b-1, c);
• 否则,返回w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1)。
• 这是一个很容易实现的函数。但存在的问题是,如果直接实现,
对于取中间值的a、b和c(例如,a=15,b=15,c=15),由于大
量递归,程序运行非常耗时
输入
• 程序的输入是一系列整数三元组,每行一个,一直到结束标志-1-
1-1为止。请您高效地计算w(a, b, c)并输出结果。
• 输出
• 输出每个三元组w(a, b, c)的值
Sample Input
1 1 1
2 2 2
10 4 6
50 50 50
-1 7 18
-1 -1 -1
Sample Output
w(1, 1, 1) = 2
w(2, 2, 2) = 4
w(10, 4, 6) = 523
w(50, 50, 50) = 1048576
w(-1, 7, 18) = 1
分析
对于取中间值的a、b和c,由于大量递归,程
序运行非常耗时。所以,本题的递归函数计
算采用记忆化递归进行计算,用一个三维的
数组f来记忆递归的结果,f[a] [b] [c]用于记忆
w(a, b, c)的返回值
记忆化递归
用一个数组存下递归结果,不是直接的递归
代码
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
//记忆化递归
const int N=20;
int f[N+1][N+1][N+1];
int w(int a,int b,int c)
{
/* if(a<=0||b<=0||c<=0)
return 1;
else if(a>20||b>20||c>20)
return w(20,20,20);
else if(a<b&&b<c)
return w(a, b, c-1)+w(a, b-1, c-1)- w(a, b-1, c);
else return w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1);
*/
if(a<=0||b<=0||c<=0)
return 1;
else if(a>N||b>N||c>N)
return w(N,N,N);
else if(f[a][b][c])
return f[a][b][c];//f[a][b][c]记忆w(a,b,c)
else if(a<b&&b<c)
return f[a][b][c]=w(a, b, c-1)+w(a, b-1, c-1)- w(a, b-1, c);
else return f[a][b][c]=w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1);
}
int main()
{
memset(f,0,sizeof(f));//三维数组初始化
int a,b,c;
while(cin>>a>>b>>c)
{
if(a==-1&&b==-1&&c==-1)
return 0;
else
cout<<"w("<<a<<", "<<b<<", "<<c<<") = "<<w(a,b,c)<<endl;
}
return 0;
}
F - Simple Addition
题目
• 输入
• 输入包含若干行。每行给出两个非负整数p和q(p≤q),这两个
整数之间用一个空格隔开,p和q是32位有符号整数。输入由包含
两个负整数的行结束。程序不用处理这一行。
• 输出
• 对于每行输入,输出一行,给出S(p, q)的值。
Sample Input
1 10
10 20
30 40
-1 -1
Sample Output
46
48
52
分析
本题递归算法:
•每一轮求出[p, q]区间内数字的个位数的和,并
计入总和;再将[p, q]区间内个位数为0的数除以
10,产生新区间,进入下一轮,再求新区间内数
字的个位数的和,并计入总和,而个位数为0的
数除以10,产生新区间;以此类推;直到[p, q]
区间内的数只有个位数
• 例如,求S(2, 53),将范围划分为3个区间:[2, 9],[10, 50]和[51,
53]。
• 对于第1个区间[2, 9],个位数之和2+3+4+……+9=44;对于第2个区
间[10, 50],个位数之和(1+2+……+9)4= 454 = 180;对于第3个区
间[51, 53],个位数之和1+2+3 = 6。所以,第一轮,个位数的总和
为44+180+6 = 230。
• 在[10, 50]中,10, 20, 30, 40和50的个位数是0,将这些数除以10后
得1, 2, 3, 4, 5,产生新区间[1, 5];进入第二轮,区间[1, 5]中的数
只有个位数,个位数之和1+2+3+4+5=15。
• 最后,两轮结果相加,得S(2, 53)=230+15=245。
递归分而治之优化
直接相加会超时,要分析题意
代码
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
typedef long long ll;
//直接累加会超时,要进行优化
ll ans;
ll f(ll n)
{
if(n%10>0)
return n%10;
else if(n==0)
return 0;
else return f(n/10);
}
void solve(ll p,ll q)//计算s(p,q)
{
if(q-p<9)//如果q-p<9直接计算
{
for(int i=p;i<=q;i++)
ans+=f(i);
return;
}
while(p%10)//第一个区间,计算个位数之和
{
ans+=f(p);
p++;
}
while(q%10)//第三个区间,计算个位数之和
{
ans+=f(q);
q--;
}
ans+=45*(q-p)/10;//第2个区间,计算个位数之和
solve(p/10,q/10);//递归进入下一轮
}
/*
int s(int p,int q)
{
for(int i=p;i<=q;i++)
{
sum+=f(i);
}
}
*/
int main()
{
ll p,q;
while(cin>>p>>q)
{
ans=0;
if(p<0&&q<0)
break;
solve(p,q);
cout<<ans<<endl;
}
}
G - A Contesting Decision
题目
对程序设计竞赛进行裁判是一项艰苦的工作,要面对要求严格的
参赛选手,要作出乏味的决定,以及要进行着单调的工作。不过,
这其中也可以有很多的乐趣。
• 对于程序设计竞赛的裁判来说,用软件使得评测过程自动化是一
个很大的帮助,而一些比赛软件存在的不可靠也得使人们希望比
赛软件能够更好、更可用。您是竞赛管理软件开发团队中的一员。
基于模块化设计原则,您所开发模块的功能是为参加程序设计竞
赛的队伍计算分数并确定冠军。给出参赛队伍在比赛中的情况,
确定比赛的冠军。
记分规则:
• 一支参赛队的记分由两个部分组成:第一部分是被解出的题数,
第二部分是罚时,表示解题总的耗费时间和试题没有被解出前错
误的提交所另加的罚时。对于每个被正确解出的试题,罚时等于
该问题被解出的时间加上每次错误提交的20分钟罚时。在问题没
有被解出前不加罚时。
• 因此,如果一支队伍在比赛20分钟的时候在第二次提交解出第1
题,他们的罚时是40分种。如果他们提交第2题3次,但没有解决
这个问题,则没有罚时。如果他们在120分钟提交第3题,并一次
解出的话,该题的罚时是120分。这样,该队的成绩是罚时160分,
解出了两道试题。
• 冠军队是解出最多试题的队伍。如果两队在解题数上打成平手,
那么罚时少的队是冠军队
输入
• 程序评判的程序设计竞赛有4题。本题设定,在计算罚时后,不会导致
队与队之间不分胜负的情况。
• 第1行 为参赛队数n
• 第2 - n+1行为每个队的参赛情况。每行的格式
• < Name > < p1Sub > < p1Time > < p2Sub > < p2Time > … < p4Time >
• 第一个元素是不含空格的队名。后面是对于4道试题的解题情况(该队
对这一试题的提交次数和正确解出该题的时间(都是整数))。如果
没有解出该题,则解题时间为0。如果一道试题被解出,提交次数至少
是一次。
• 输出
• 输出一行。给出优胜队的队名,解出题目的数量,以及罚时。
Sample Input
4
Stars 2 20 5 0 4 190 3 220
Rockets 5 180 1 0 2 0 3 100
Penguins 1 15 3 120 1 300 4 0
Marsupials 9 0 3 100 2 220 3 80
Sample Output
Penguins 3 475
分析
简单结构体排序,也可在处理过程中进行比较,记下队名,题数,罚时
代码
#include<iostream>
#include<algorithm>
#include<string>
using namespace std;
typedef struct
{
string name;
int sub[5];
int time[5];
int sumnum;
int sumtime;
}team;
team t[100010];
bool cmp(team a,team b)
{
if(a.sumnum==b.sumnum)
return a.sumtime<b.sumtime;
else
return a.sumnum>b.sumnum;
}
int main()
{
int n;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>t[i].name;
t[i].sumnum=0;
t[i].sumtime=0;
for(int j=0;j<4;j++)
{
cin>>t[i].sub[j]>>t[i].time[j];
if(t[i].time[j]!=0)
{
t[i].sumnum++;
t[i].sumtime=t[i].sumtime+t[i].time[j]+20*(t[i].sub[j]-1);
}
}
}
sort(t,t+n,cmp);
cout<<t[0].name<<" "<<t[0].sumnum<<" "<<t[0].sumtime;
}