2008年NOIP普及组T1- ISBN号码
题目描述
每一本正式出版的图书都有一个ISBN号码与之对应,ISBN码包括9位数字、1位识别码和3位分隔符,其规定格式如“x-xxx-xxxxx-x”,其中符号“-”就是分隔符(键盘上的减号),最后一位是识别码,例如0-670-82162-4就是一个标准的ISBN码。ISBN码的首位数字表示书籍的出版语言,例如0代表英语;第一个分隔符“-”之后的三位数字代表出版社,例如670代表维京出版社;第二个分隔符后的五位数字代表该书在该出版社的编号;最后一位为识别码。
识别码的计算方法如下:
首位数字乘以1加上次位数字乘以2……以此类推,用所得的结果mod 11,所得的余数即为识别码,如果余数为10,则识别码为大写字母X。例如ISBN号码0-670-82162-4中的识别码4是这样得到的:对067082162这9个数字,从左至右,分别乘以1,2,...,9,再求和,即0×1+6×2+……+2×9=158,然后取158 mod 11的结果4作为识别码。
你的任务是编写程序判断输入的ISBN号码中识别码是否正确,如果正确,则仅输出“Right”;如果错误,则输出你认为是正确的ISBN号码。
输入格式
输入文件isbn.in只有一行,是一个字符序列,表示一本书的ISBN号码(保证输入符合ISBN号码的格式要求)。
输出格式
输出文件isbn.out共一行,假如输入的ISBN号码的识别码正确,那么输出“Right”,否则,按照规定的格式,输出正确的ISBN号码(包括分隔符“-”)。
输入输出样例
输入样例1:
0-670-82162-4
输出样例1:
Right
输入样例2:
0-670-82162-0
输出样例2:
0-670-82162-4
耗时限制1000ms 内存限制128MB
解析
考点:字符串
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
int cnt = 0, yu, sum = 0;
getline(cin, s);
for (int i = 0; i <= 11; i++) {
if (s[i] != '-') {
cnt++;
sum += (s[i] - '0') * cnt;
}
}
yu = sum % 11;
if (yu == 10) {
if (s[12] == 'X') cout << "Right";
else {
s[12] = 'X';
cout << s;
}
} else {
if (s[12] == yu + '0') cout << "Right";
else {
s[12] = yu + '0';
cout << s;
}
}
return 0;
}
2008年NOIP普及组T2- 排座椅
题目描述
上课的时候总会有一些同学和前后左右的人交头接耳,这是令小学班主任十分头疼的一件事情。不过,班主任小雪发现了一些有趣的现象,当同学们的座次确定下来之后,只有有限的D对同学上课时会交头接耳。同学们在教室中坐成了M行N列,坐在第i行第j列的同学的位置是(i,j),为了方便同学们进出,在教室中设置了K条横向的通道,L条纵向的通道。于是,聪明的小雪想到了一个办法,或许可以减少上课时学生交头接耳的问题:她打算重新摆放桌椅,改变同学们桌椅间通道的位置,因为如果一条通道隔开了两个会交头接耳的同学,那么他们就不会交头接耳了。
请你帮忙给小雪编写一个程序,给出最好的通道划分方案。在该方案下,上课时交头接耳的学生的对数最少。
输入格式
输入文件seat.in的第一行,有5个用空格隔开的整数,分别是M,N,K,L,D(2<=N,M<=1000,0<=K<M,0<=L<N,D<=2000)。
接下来的D行,每行有4个用空格隔开的整数。第i行的4个整数Xi,Yi,Pi,Qi,表示坐在位置(Xi,Yi)与(Pi,Qi)的两个同学会交头接耳(输入保证他们前后相邻或者左右相邻)。
输入数据保证最优方案的唯一性。
输出格式
输出文件seat.out共两行。
第一行包含K个整数,a1,a2……aK,表示第a1行和a1+1行之间、第a2行和a2+1行之间、…、第aK行和第aK+1行之间要开辟通道,其中ai< ai+1,每两个整数之间用空格隔开(行尾没有空格)。
第二行包含L个整数,b1,b2……bL,表示第b1列和b1+1列之间、第b2列和b2+1列之间、…、第bL列和第bL+1列之间要开辟通道,其中bi< bi+1,每两个整数之间用空格隔开(列尾没有空格)。
输入输出样例
输入样例1:
4 5 1 2 3 4 2 4 3 2 3 3 3 2 5 2 4
输出样例1:
2 2 4
说明
【样例说明】
上图中用符号*、※、+标出了3对会交头接耳的学生的位置,图中3条粗线的位置表示通道,图示的通道划分方案是唯一的最佳方案。
耗时限制1000ms 内存限制128MB
解析:
考点:贪心
思路
这道题目的核心,是需要发现如下性质:
不同行、列之间是完全独立的。即不管将哪行、哪列切开,对其余的行列都是没有任何影响的。
因此可以分别考虑行和列。
对于行来说,问题变成:
去掉哪K行,可以使得最后剩下的行间的边数最少。这里去掉边数最多的
K行一定是最优的。否则可以将选出的行替换成边数最多的 K 行,且结果不会变差。
时间复杂度
算法瓶颈在排序上,因此时间复杂度是 :O(nlogn)。
参考代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
const int N = 1010;
int n, m, L, K, D;
PII row[N], col[N];
int q[N];
int main(){
cin >> n >> m >> K >> L >> D;
for (int i = 1; i <= n; i ++ ) row[i].second = i;
for (int i = 1; i <= m; i ++ ) col[i].second = i;
while (D -- ){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
if (abs(x1 - x2) == 1) row[min(x1, x2)].first ++ ;
else col[min(y1, y2)].first ++ ;
}
sort(row + 1, row + n + 1);
sort(col + 1, col + m + 1);
int cnt = 0;
for (int i = n; i > n - K; i -- ) q[cnt ++ ] = row[i].second;
sort(q, q + cnt);
for (int i = 0; i < cnt; i ++ ) printf("%d ", q[i]);
puts("");
cnt = 0;
for (int i = m; i > m - L; i -- ) q[cnt ++ ] = col[i].second;
sort(q, q + cnt);
for (int i = 0; i < cnt; i ++ ) printf("%d ", q[i]);
puts("");
return 0;
}
2008年NOIP普及组T3- 传球游戏
题目描述
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师在此吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的 方式有1->2->3->1和1->3->2->1,共2种。
输入格式
输入文件ball.in共一行,有两个用空格隔开的整数n,m(3<=n<=30,1<=m<=30)。
输出格式
输出文件ball.out共一行,有一个整数,表示符合题意的方法数。
输入输出样例
输入样例1:
3 3
输出样例1:
2
说明
【限制】
40%的数据满足:3<=n<=30,1<=m<=20
100%的数据满足:3<=n<=30,1<=m<=30
耗时限制1000ms 内存限制128MB
解析
考点:动态规划,线性DP
思路
设小蛮在0号,所有人的编号是 0∼n−1。
状态表示 f[i, j]
:
- 集合:所有已经传了i次球,且最后球在编号是j的小朋友手上的方案;
- 属性:集合中元素的数量;
状态计算:
f[i, j]
所表示的集合可以划分成两个子集:- 从左边传过来的集合大小是
f[i, j - 1]
; - 从右边传过来的集合大小是
f[i, j + 1]
;
- 从左边传过来的集合大小是
f[i, j]
等于两个子集的元素数之和。 注意当j = 0
或j = n - 1
时需要特殊处理边界。
最终答案就是f[m][0]
。
时间复杂度
总共有 NM 个状态,计算每个状态需要 O(1) 的时间,因此总时间复杂度是 O(NM)。
参考代码:
#include <iostream>
using namespace std;
const int N = 35;
int n, m;
int f[N][N];
int main(){
cin >> n >> m;
f[0][0] = 1;
for (int i = 1; i <= m; i ++ )
for (int j = 0; j < n; j ++ )
f[i][j] = f[i - 1][(j + n - 1) % n] + f[i - 1][(j + 1) % n];
cout << f[m][0] << endl;
return 0;
}
2008年NOIP普及组T4- 立体图
题目描述
题目描述:
小渊是个聪明的孩子,他经常会给周围的小朋友们将写自己认为有趣的内容。最近,他准备给小朋友们讲解立体图,请你帮他画出立体图。
小渊有一块面积为m*n的矩形区域,上面有m*n个边长为1的格子,每个格子上堆了一些同样大小的积木(积木的长宽高都是1),小渊想请你打印出这些格子的立体图。我们定义每个积木为如下格式,并且不会做任何翻转旋转,只会严格以这一种形式摆放:
每个顶点用1个加号’+’表示,长用3个”-”表示,宽用1个”/”,高用两个”|”表示。字符’+’,”-”,”/”,”|”的ASCII码分别为43,45,47,124。字符’.’(ASCII码46)需要作为背景输出,即立体图里的空白部分需要用’.’来代替。立体图的画法如下面的规则:
若两块积木左右相邻,图示为:
若两块积木上下相邻,图示为:
若两块积木前后相邻,图示为:
立体图中,定义位于第(m,1)的格子(即第m行第1列的格子)上面自底向上的第一块积木(即最下面的一块积木)的左下角顶点为整张图最左下角的点。
输入格式
输入文件drawing.in第一行有用空格隔开的2个整数m和n,表示有m*n个格子(1<=m,n<=50)。
接下来的m行,是一个m*n的矩阵,每行有n个用空格隔开的整数,其中第i行第j列上的整数表示第i行第j列的个子上摞有多少个积木(1<=每个格子上的积木数<=100)。
输出格式
输出文件drawing.out中包含题目要求的立体图,是一个K行L列的字符串矩阵,其中K和L表示最少需要K行L列才能按规定输出立体图。
输入输出样例
输入样例1:
3 4 2 2 1 2 2 2 1 1 3 2 1 2
输出样例1:
......+---+---+...+---+
..+---+ / /|../ /|
./ /|-+---+ |.+---+ |
+---+ |/ /| +-| | +
| | +---+ |/+---+ |/|
| |/ /| +/ /|-+ |
+---+---+ |/+---+ |/| +
| | | +-| | + |/.
| | |/ | |/| +..
+---+---+---+---+ |/...
| | | | | +....
| | | | |/.....
+---+---+---+---+......
- 耗时限制1000ms 内存限制128MB
解析
考点:模拟,字符串
我们发现我们只用绘制三个面。根据坐标的规律,我们分别对这一块的坐标进行绘制。
同时我们可以发现这个图形坐标的规律。假设一个立方体的高度为z,所在坐标为x,y(整个矩阵中,左下角的坐标为(0,0))坐标,那么这个立方体的左下角坐标为:
x=2∗y+4∗x
y=2y+3z
通过上面的图,也可以知道整个图的右上角坐标为:
x=2y+4x+6
y=2y+3z+5
我们将这个右上角坐标取最大值,就是整张图的范围。
然后按照顺序进行绘制就可以了。注意,无论是输入还是输出还是处理,都要使用【左下角】坐标是(0,0)的思想,这样会使过程更加直观容易理解,但如果写代码的思路不清晰很可能会弄错。
#include<bits/stdc++.h>
using namespace std;
const int N=10000;
char mz[N][N];
int Z[N][N];
void mdraw(int x,int y){
/*正面矩阵绘制*/
mz[x][y+3]=mz[x][y]=mz[x+4][y]=mz[x+4][y+3]='+';
for(int i=x+1;i<x+4;i++)
mz[i][y]=mz[i][y+3]='-';
for(int i=y+1;i<y+3;i++)
mz[x][i]=mz[x+4][i]='|';
for(int i=x+1;i<x+4;i++)
for(int j=y+1;j<y+3;j++)
mz[i][j]=' ';
/*顶面矩阵绘制*/
mz[x+1][y+4]=mz[x+5][y+4]='/';
mz[x+2][y+5]=mz[x+6][y+5]='+';
for(int i=x+2;i<=x+4;i++)
mz[i][y+4]=' ';
for(int i=x+3;i<=x+5;i++)
mz[i][y+5]='-';
/*侧面矩阵绘制*/
mz[x+5][y+1]='/';
mz[x+6][y+2]='+';
mz[x+5][y+2]=mz[x+5][y+3]=' ';
mz[x+6][y+3]=mz[x+6][y+4]='|';
}
void putout(int x,int y){
for(int j=y;j>=0;j--){
for(int i=0;i<=x;i++)
if(!mz[i][j])putchar('.');
else putchar(mz[i][j]);
putchar('\n');
}//按照平面直角坐标系顺序进行输出
}
int main(){
int mx=0,my=0;
int m,n;
scanf("%d%d",&m,&n);
for(int y=m-1;y>=0;y--)
for(int x=0;x<n;x++)
scanf("%d",&Z[x][y]); //注意输入
for(int y=m-1;y>=0;y--)
for(int x=0;x<n;x++)
for(int z=0;z<=Z[x][y]-1;z++){
mx=max(mx,2*y+4*x+6);
my=max(my,2*y+3*z+5);
mdraw(2*y+4*x,2*y+3*z);
} //注意绘制顺序
putout(mx,my); //输出
return 0;
}