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
解析
考点:DFS、DFS 求最少步数,深搜,剪枝
思路:
深搜求走到每个点的最少步数:
分情况讨论:
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;
}