2008年NOIP普及组复赛真题解析

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 = 0j = 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值