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

2017年NOIP普及组T1-成绩 [score]

题目描述

牛牛最近学习了C++入门课程,这门课程的总成绩计算方法是:

总成绩=作业成绩×20%+小测成绩×30%+期末考试成绩×50%

牛牛想知道,这门课程自己最终能得到多少分。

输入格式

输入文件名为 score.in。

输入文件只有 1 行,包含三个非负整数A、B、C,分别表示牛牛的作业成绩、小测 成绩和期末考试成绩。相邻两个数之间用一个空格隔开,三项成绩满分都是 100 分。

输出格式

输出文件名为 score.out。

输出文件只有 1 行,包含一个整数,即牛牛这门课程的总成绩,满分也是 100 分。

输入输出样例

输入样例1:
100 100 80
输出样例1:
90
输入样例2:
60 90 80
输出样例2:
79

说明

【输入输出样例 1 说明】

牛牛的作业成绩是 100 分,小测成绩是 100 分,期末考试成绩是 80 分,总成绩是 100×20%+100×30%+80×50%=20+30+40=90。

【输入输出样例 2 说明】

牛牛的作业成绩是 60 分,小测成绩是 90 分,期末考试成绩是 80 分,总成绩是60 × 20% + 90 × 30% + 80 × 50% = 12 + 27 + 40 = 79。

【数据规模与约定】

对于 30% 的数据,A=B=0。

对于另外 30% 的数据,A = B = 100。

对于 100% 的数据, 0 ≤ A、B、C ≤ 100 且 A、B、C 都是 10 的整数倍。

耗时限制1000ms   内存限制256MB

解析:

考点:输入输出

参考代码:

#include <bits/stdc++.h>
using namespace std;
int a,b,c;
int r;
int main(){
	cin>>a>>b>>c;
	r = a * 0.2 + b * 0.3 + c * 0.5;
	cout<<r;
	return 0;
}

2017年NOIP普及组T2-图书管理员 [librarian]

题目描述

图书馆中每本书都有一个图书编码,可以用于快速检索图书,这个图书编码是一个 正整数。

每位借书的读者手中有一个需求码,这个需求码也是一个正整数。如果一本书的图书编码恰好以读者的需求码结尾,那么这本书就是这位读者所需要的。

小 D 刚刚当上图书馆的管理员,她知道图书馆里所有书的图书编码,她请你帮她写 一个程序,对于每一位读者,求出他所需要的书中图书编码最小的那本书,如果没有他需要的书,请输出-1。

输入格式

输入文件名为 librarian.in。

输入文件的第一行,包含两个正整数 n 和 q,以一个空格分开,分别代表图书馆里书的数量和读者的数量。

接下来的 n 行,每行包含一个正整数,代表图书馆里某本书的图书编码。接下来的 q 行,每行包含两个正整数,以一个空格分开,第一个正整数代表图书馆里读者的需求码的长度,第二个正整数代表读者的需求码。

输出格式

输出文件名为 librarian.out。

输出文件有 q 行,每行包含一个整数,如果存在第 i 个读者所需要的书,则在第 i行输出第 i 个读者所需要的书中图书编码最小的那本书的图书编码,否则输出-1。

输入输出样例

输入样例1:
5 5
2123
1123
23
24
24
2 23
3 123
3 124
2 12
2 12
输出样例1:
23
1123
-1
-1
-1

说明

【输入输出样例 1 说明】

第一位读者需要的书有 2123、1123、23,其中 23 是最小的图书编码。

第二位读者需要 的书有 2123、1123,其中 1123 是最小的图书编码。

对于第三位,第四位和第五位读者,没有书的图书编码以他们的需求码结尾,即没有他们需要的书,输出-1。

【数据规模与约定】

对于 20%的数据,1 ≤ n ≤ 2。

另有 20%的数据,q = 1。

另有 20%的数据,所有读者的需求码的长度均为 1。

另有 20%的数据,所有的图书编码按从小到大的顺序给出。

对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ q ≤ 1,000,所有的图书编码和需求码均 不超过 10,000,000。

耗时限制1000ms   内存限制256MB

解析

考点:模拟,数组,循环结构

思路:

一本书的图书编码恰好以读者的需求码结尾

求:能匹配的书籍中,图书编码最小的那本书

问:如果有一本书的编码为 a[i],有 len 位的需求码 x

能否匹配? a[i]是否以 x 结尾

a[i]%10^len == x,说明能匹配

参考代码

#include <bits/stdc++.h>
using namespace std;
int n,q;
int a[1010];
int main(){
	cin>>n>>q;
	//图书编码
	for(int i = 1;i <= n;i++) cin>>a[i];
	//q 次询问
	int len,x,ans;
	while(q--){
		cin>>len>>x;
		//循环所有的图书编码,求能匹配的最小的编码
		ans = INT_MAX;
		for(int i = 1;i <= n;i++){
			if(a[i]%(int)pow(10,len) == x){
				if(a[i] < ans) ans = a[i];
			}
		}
		if(ans == INT_MAX) cout<<-1<<endl;
		else cout<<ans<<endl;
	}
	return 0;
}

2017年NOIP普及组T3-棋盘 [chess]

题目描述

有一个m × m的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在 要从棋盘的最左上角走到棋盘的最右下角。 任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的),你只能向上、下、

左、右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你 不需要花费金币;如果不同,则你需要花费 1 个金币。

另外,你可以花费 2 个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个 魔法不能连续使用,而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走 到了这个暂时有颜色的格子上,你就不能继续使用魔法;只有当你离开这个位置,走到一个 本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔 法使得变为有颜色的格子)时,这个格子恢复为无色。

现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?

输入格式

输入文件名为 chess.in。

数据的第一行包含两个正整数 m,n,以一个空格分开,分别代表棋盘的大小,棋盘上 有颜色的格子的数量。

接下来的 n 行,每行三个正整数 x,y,c,分别表示坐标为(x,y)的格子有颜色 c。 其中 c=1 代表黄色,c=0 代表红色。相邻两个数之间用一个空格隔开。棋盘左上角的坐标 为(1, 1),右下角的坐标为(m, m)。

棋盘上其余的格子都是无色。保证棋盘的左上角,也就是(1,1)一定是有颜色的。

输出格式

输出文件名为 chess.out。

输出一行,一个整数,表示花费的金币的最小值,如果无法到达,输出-1。

输入输出样例

输入样例1:
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0
输出样例1:
8
输入样例2:
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0
输出样例2:
-1

说明

【输入输出样例 1 说明】

从(1,1)开始,走到(1,2)不花费金币

从(1,2)向下走到(2,2)花费 1 枚金币

从(2,2)施展魔法,将(2,3)变为黄色,花费 2 枚金币

从(2,2)走到(2,3)不花费金币

从(2,3)走到(3,3)不花费金币

从(3,3)走到(3,4)花费 1 枚金币

从(3,4)走到(4,4)花费 1 枚金币

从(4,4)施展魔法,将(4,5)变为黄色,花费 2 枚金币,

从(4,4)走到(4,5)不花费金币

从(4,5)走到(5,5)花费 1 枚金币

共花费 8 枚金币。

【输入输出样例 2 说明】

从(1,1)走到(1,2),不花费金币

从(1,2)走到(2,2),花费 1 金币

施展魔法将(2,3)变为黄色,并从(2,2)走到(2,3)花费 2 金币

从(2,3)走到(3,3)不花费金币 从(3,3)只能施展魔法到达(3,2),(2,3),(3,4),(4,3) 而从以上四点均无法到达(5,5),故无法到达终点,输出-1

【数据规模与约定】

对于 30%的数据,1 ≤ m ≤ 5, 1 ≤ n ≤ 10。

对于 60%的数据,1 ≤ m ≤ 20, 1 ≤ n ≤ 200。

对于100%的数据,1 ≤ m ≤ 100, 1 ≤ n ≤ 1,000。

耗时限制1000ms   内存限制256MB

解析

考点:DFSDFS 求最少步数,深搜,剪枝

思路:

深搜求走到每个点的最少步数:

分情况讨论:

1、如果要走到的点有颜色,那么分为同色或者不同色,同色不消耗金币,不同色消耗 金币;

2、如果要走到的点没有颜色,那么要求走到当前点的必须没有用过魔法,也就是要将 走到每个点是否使用魔法的状态作为参数带入递归;

如果使用魔法修改颜色, 那么修改后在递归后退时,需要撤销该颜色的修改(回溯)。

分析:

1.从左上角走到右下角

2.所在的位置必须有颜色

3.同色不花金币,不同色花 1 个金币

4. 2 个金币,将下一个位置改成和当前位置同色

不能连续用魔法:因此要存储走到当前点是否用过魔法的状态

离开使用魔法修改过颜色的位置后,恢复无色;

问:从左上角走到右下角的最少花费的金币数量。

思路:求最少“步数”

分两种情况讨论:

1.要去的点有颜色: a.颜色相同 b.颜色不同

2.要去的点没有颜色:

走到当前点是否用过魔法

如果没有用:就可以用魔法修改下个位置的颜色,花 2 金币

c=1 代表黄色, c=0 代表红色,将颜色+1 和没有赋值区分开。

参考代码

#include <bits/stdc++.h>
using namespace std;
//a:存储棋盘 d:存储走到每个点的最少金币的数量
int m,n,a[110][110],d[110][110];
int fx[5] = {0,0,1,0,-1};
int fy[5] = {0,1,0,-1,0};
//深搜求走到每个点的最少金币数
void dfs(int x,int y,int sum,bool magic){
	d[x][y] = sum;//更新走到当前点的最少金币数
	//尝试四方向
	int tx,ty;
	for(int i = 1;i <= 4;i++){
		tx = x + fx[i];
		ty = y + fy[i];
		//出棋盘
		if(!(tx>=1&&tx<=m&&ty>=1&&ty<=m)) continue;
		//要去的点有颜色
		if(a[tx][ty] != 0){
			if(a[tx][ty]==a[x][y]&&sum<d[tx][ty]) dfs(tx,ty,sum,false);
			else if(a[tx][ty]!=a[x][y]&&sum+1<d[tx][ty]) dfs(tx,ty,sum+1,false); 
		}else{
			//没有颜色
			//走到当前点没有用过魔法
			if(magic == false){
				if(sum+2<d[tx][ty]){
					a[tx][ty] = a[x][y];
					dfs(tx,ty,sum+2,true);
					a[tx][ty] = 0;
				}
			}
		}
	}
}
int main(){
	cin>>m>>n;
	//读入 n 个有颜色的格子
	int x,y,c;
	for(int i = 1;i <= n;i++){
		cin>>x>>y>>c;
		a[x][y] = c + 1;
	}
	memset(d,0x3f,sizeof(d));
	dfs(1,1,0,false);
	//如果没有走到最后一个点
	if(d[m][m] == 0x3f3f3f3f) cout<<-1;
	else cout<<d[m][m];
	return 0;
}
动态规划写法

f[i][j][k][l]表示到(i,j)点颜色为k时的最小金币花费数。。。然后那个l就是上一次用没用魔法
通过四个方向更新
坑点,就是要在DP时循环4遍,因为可能有好几个方向能更新f。 



#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 205;
const int inf = 210000000 ;
int w[M][M];
int m,n,x,y,z;
int ans=210000000;
int f[M][M][3][2];
int main(){
    int i,j,k,l;
    scanf("%d%d",&m,&n);
    for(i=1;i<=n;i++)    {
        scanf("%d%d%d",&x,&y,&z);
        w[x][y]=z+1;
    }
    memset(f,127/3,sizeof(f));
    f[1][1][w[1][1]][1]=0;
    int T=4;
    while(T--)
    for(i=1;i<=m;i++)
      for(j=1;j<=m;j++){
          if(i==1&&j==1) continue;
          if(w[i][j]){
            for(k=1;k<=2;k++)
            for(l=0;l<=1;l++)
              if(w[i][j]==k){
                  int temp=f[i][j][k][1];
                  f[i][j][w[i][j]][1]=min(f[i-1][j][k][l],min(f[i][j-1][k][l],min(f[i][j+1][k][l],f[i+1][j][k][l])));
                  f[i][j][w[i][j]][1]=min(temp,f[i][j][k][1]);
              }
              else{
                  int temp=f[i][j][w[i][j]][1];
                  f[i][j][w[i][j]][1]=min(f[i-1][j][k][l],min(f[i][j-1][k][l],min(f[i][j+1][k][l],f[i+1][j][k][l])))+1;
                  f[i][j][w[i][j]][1]=min(temp,f[i][j][w[i][j]][1]);
              }
        }
        else
        for(int k=1;k<=2;k++){
            int temp=f[i][j][k][0];
            f[i][j][k][0]=min(f[i-1][j][k][1],min(f[i][j-1][k][1],min(f[i][j+1][k][1],f[i+1][j][k][1])))+2;
            f[i][j][k][0]=min(temp,f[i][j][k][0]);
        }
      }
    ans=min(ans,min(f[m][m][1][1],min(f[m][m][1][0],min(f[m][m][2][1],f[m][m][2][0]))));
    if(ans==inf)
        printf("-1\n");
    else
        printf("%d\n",ans);
    return 0;
}

2017年NOIP普及组T4-跳房子 [jump]

题目描述

跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。跳房子的游戏规则如下:

在地面上确定一个起点,然后在起点右侧画n个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个格子能得到的分数。玩家第一次从起点开始向 右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定: 玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分 数为曾经到达过的格子中的数字之和。

现在小R研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的 缺陷,它每次向右弹跳的距离只能为固定的d。小R 希望改进他的机器人,如果他花 g个金 币改进他的机器人,那么他的机器人灵活性就能增加g,但是需要注意的是,每次弹跳的距 离至少为1。具体而言,当g< d时,他的机器人每次可以选择向右弹跳的距离为 d-g, d-g+1, d-g+2,...,d+g-2,d+g-1,d+g;否则(当g ≥ d时),他的机器人每次可以选择向右弹跳的 距离为 1,2,3,...,d+g-2,d+g-1,d+g。

现在小R希望获得至少k分,请问他至少要花多少金币来改造他的机器人。

输入格式

输入文件名为jump.in。

第一行三个正整数n,d,k分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数之间用一个空格隔开。

接下来n行,每行两个正整数xi, si,分别表示起点到第i个格子的距离以及第i个格子的分数。两个数之间用一个空格隔开。保证xi按递增顺序输入。

输出格式

输出文件名为jump.out。

共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少k分,输出-1。

输入输出样例

输入样例1:
7 4 10 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2
输出样例1:
2
输入样例2:
7 4 20 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2
输出样例2:
-1

说明

【输入输出样例 1 说明】

花费2个金币改进后,小R的机器人依次选择的向右弹跳的距离分别为2,3,5,3,4,3,先后到达的位置分别为 2,5,10,13,17,20,对应1, 2, 3, 5, 6, 7这6个格子。这些格子中的数字之和15即为小R获得的分数。

【输入输出样例 2 说明】

由于样例中7个格子组合的最大可能数字之和只有18,无论如何都无法获得20 分

【数据规模与约定】

本题共10组测试数据,每组数据10分。

对于全部的数据满足1 ≤n≤ 500000, 1 ≤d≤ 2000,

对于第1,2 组测试数据,n≤ 10;

对于第3,4,5 组测试数据,n≤ 500;

对于第6,7,8 组测试数据,d=1

耗时限制1000ms   内存限制256MB

解析

考点:动态规划,二分答案,单调队列优化

思路:

1、由于花的金币越多,弹跳范围越大,得分也就一定不递减, 因此本题的答案符合单调 性,可以二分。

2、对于每个点, 可以枚举每个点的前一个跳跃点, DP 求最优。

 f[i]代表:在第 i 个位置能得到的最大得分。

f[i] = max(f[i],f[j] + s[i])

g,弹跳范围:d-g ~ d+g

g=2,弹跳范围:d-2~d+2

g=3,弹跳范围:d-3~d+3

因此, d-3~d+3,包含了 d-2~d+2 的求解。

二分答案: g 的范围是有限的, 一定在 1~n 之间。

动归求到每个点的最大值:

f[i]:到 i 点的最大值。

解法一:枚举每个点左侧可能的跳跃点,求跳跃到每个点可能得到的分数的最大值。

每次向右弹跳的距离只能为固定的 d

1.二分答案

2.检查在花费了 g 个金币的情况下,能否得到>=k 

最终求解左边界

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
typedef long long LL;
LL f[N];
int x[N],s[N];//x:距离, s:分值
int n,d,k;
//检验函数:如果花了 g 个金币,能否得到 k 分
bool check(int g){
	int mi = max(d - g,1);
	int ma = d + g;
	memset(f,-0x3f,sizeof(f));
	f[0] = 0;//边界条件
	//DP 走到每个点的最大得分
	for(int i = 1;i <= n;i++){
		//枚举前一个跳跃点
		for(int j = i - 1;j >= 0;j--){
		//如果在 j 这个点,跳最大距离也到不了 i break
			if(x[j] + ma < x[i]) break;
			//如果跳跃最小的距离,跳过了
			if(x[j] + mi > x[i]) continue;
			f[i] = max(f[i],f[j] + s[i]);
			if(f[i] >= k) return true;
		}
	}
	return false;
}
int main(){
	scanf("%d%d%d",&n,&d,&k);
	//读入每个点的距离和分数
	for(int i = 1;i <= n;i++){
		scanf("%d%d",&x[i],&s[i]);
	}
	//二分答案,求左边界
	int l = 0,r = 2000;
	while(l <= r){
		int mid = l + r >> 1;
		//如果花了 mid 个金币能跳过去
		if(check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	if(l == 2001) cout<<-1;
	else cout<<l;
	return 0;
}

解法二:单调队列优化

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
typedef long long LL;
LL f[N];
int x[N],s[N];//x:距离, s:分值
int n,d,k;
int q[N],h,t;//单调队列
//检验函数:如果花了 g 个金币,能否得到 k 分
bool check(int g){
	int mi = max(d - g,1);
	int ma = d + g;
	memset(f,-0x3f,sizeof(f));
	f[0] = 0;//边界条件
	h = 1,t = 0;
	int b = 0,e = 0;//代表区间的左右端点的位置
	
	//DP 走到每个点的最大得分
	for(int i = 1;i <= n;i++){
	//如果右端点可以移动
	while(x[i] - x[e] >= mi){
		//去除队列中的无效值
		while(h <= t && f[e] >= f[q[t]]) t--;
			t++;
			q[t] = e;
			e++;
		}
		//左端点能否向右移动
		while(x[i] - x[b] > ma) b++;
		//如果队列队首已经不再区间范围,弹出队首元素
		while(h<=t&&q[h]<b) h++;
		if(h <= t) f[i] = f[q[h]] + s[i];
		if(f[i] >= k) return true;
	}
	return false;
}
int main(){
	scanf("%d%d%d",&n,&d,&k);
	//读入每个点的距离和分数
	for(int i = 1;i <= n;i++){
		scanf("%d%d",&x[i],&s[i]);
	}
	//二分答案,求左边界
	int l = 0,r = x[n];
	while(l <= r){
		int mid = l + r >> 1;
		//如果花了 mid 个金币能跳过去
		if(check(mid)) r = mid - 1;
		else l = mid + 1;
	}
	if(l == x[n] + 1) cout<<-1;
	else cout<<l;
	return 0;
}

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值