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

2015年NOIP普及组T1-金币 [coin]

题目描述

国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天 (第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚 金币;之后四天(第七、八、九、十天),每天收到四枚金币......;这种工资发放模式 会一直这样延续下去:当连续 N 天每天收到 N 枚金币后,骑士会在之后的连续 N+1 天 里,每天收到 N+1 枚金币。

请计算在前 K 天里,骑士一共获得了多少金币。

输入格式

输入文件名:coin.in

输入文件只有 1 行,包含一个正整数 K,表示发放金币的天数。(1 ≤ K ≤ 10,000)

输出格式

输出文件名:coin.out

输出文件只有 1 行,包含一个正整数,即骑士收到的金币数。

输入输出样例

输入样例1:

6

输出样例1:

14

输入样例2:
1000
输出样例2:
29820

说明

【输入输出样例 1 说明】

骑士第一天收到一枚金币;第二天和第三天,每天收到两枚金币;第四、五、六天, 每天收到三枚金币。因此一共收到 1+2+2+3+3+3=14 枚金币。

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

解析

考点:循环,模拟

价值为 k 的金币,要发放 k
思路:通过计数器 c 统计价值为 k 的金币是否发到了 k

参考代码

#include <bits/stdc++.h>
using namespace std;
int n,k = 1,c = 0;
int sum = 0;
int main(){
	 cin>>n;
	 for(int i = 1;i <= n;i++){
	 	sum += k;
	 	c++;
	 	if(c == k){
	 		k++;
	 		c = 0;
	 	}
	 }
	 cout<<sum;
	return 0;
}

2015年NOIP普及组T2-扫雷游戏(mine)

题目描述

扫雷游戏是一款十分经典的单机小游戏。在n行m列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻开一个非地雷格时,该格将会出现一个数字——提示周围格子中有多少个是地雷格。游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格。
现在给出n行m列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数。
注:一个格子的周围格子包括其上、下、左、右、左上、右上、左下、右下八个方向上与之直接相邻的格子。

输入格式

第一行是用一个空格隔开的两个整数n和m,分别表示雷区的行数和列数。
接下来n行,每行m个字符,描述了雷区中的地雷分布情况。字符’*’表示相应格子是地雷格,字符’?’表示相应格子是非地雷格。

输出格式

输出文件包含n行,每行m个字符,描述整个雷区。用’*’表示地雷格,用周围的地雷个数表示非地雷格。相邻字符之间无分隔符。

样例
输入#1
3 3
*??
???
?*?
输出#1
*10
221
1*1

输入#2

2 3
?*?
*??
输出#2
2*1
*21
数据范围

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

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

解析

考点:模拟,二维数组

思路:逐个点判断,如果是非地雷格,求出该点八方向有几个地雷。

参考代码

#include <bits/stdc++.h>
using namespace std;
char a[110][110];
int n,m;
int main(){
	 cin>>n>>m;
	 for(int i = 1;i <= n;i++){
		 for(int j = 1;j <= m;j++){
		 	cin>>a[i][j];
		 }
	 }
	 //计算每个非地雷格八方向中有几个地雷
	 int c;
	 for(int i = 1;i <= n;i++){
		 for(int j = 1;j <= m;j++){
			 if(a[i][j] == '*') cout<<'*';
			 else{
				 //计算 8 方向的地雷的数量
				 c = 0;
				 if(a[i-1][j-1]=='*') c++;
				 if(a[i-1][j]=='*') c++;
				 if(a[i-1][j+1]=='*') c++;
				 if(a[i][j+1]=='*') c++;
				 if(a[i+1][j+1]=='*') c++;
				 if(a[i+1][j]=='*') c++;
				 if(a[i+1][j-1]=='*') c++;
				 if(a[i][j-1]=='*') c++;
			 	 cout<<c;
			 }
		 }
		 cout<<endl;
	 }
	return 0;
}

2015年NOIP普及组T3- 求和 [sum]

题目描述

一条狭长的纸带被均匀划分出了n个格子,格子编号从1到n。每个格子上都染了一种颜色color_i用[1,m]当中的一个整数表示),并且写了一个数字number_i。

定义一种特殊的三元组:(x,y,z),其中x,y,z都代表纸带上格子的编号,这里的三元组要求满足以下两个条件:

1、xyz是整数,x<y<z,y-x=z-y

2、colorx=colorz

满足上述条件的三元组的分数规定为(x + z) * (number_x + number_z)。整个纸带的分数规定为所有满足条件的三元组的分数的和。这个分数可能会很大,你只要输出整个纸带的分数除以 10007 所得的余数即可。

输入格式

输入文件名为 sum.in。

第一行是用一个空格隔开的两个正整数n和m,n表纸带上格子的个数,m表纸带上颜色的种类数。

第二行有n用空格隔开的正整数,第i数字number表纸带上编号为i格子上面写的数字。

第三行有n用空格隔开的正整数,第i数字color表纸带上编号为i格子染的颜色。

输出格式

输出文件名为 sum.out。

共一行,一个整数,表示所求的纸带分数除以 10,007 所得的余数。

输入输出样例

输入样例1:
6 2
5 5 3 2 2 2
2 2 1 1 2 1
输出样例1:
82
输入样例2:
15 4
5 10 8 2 2 2 9 9 7 7 5 6 4 2 4
2 2 3 3 4 3 3 2 4 4 4 4 1 1 1
输出样例2:
1388

说明

【输入输出样例 1 说明】

纸带如题目描述中的图所示。

所有满足条件的三元组为: (1, 3, 5), (4, 5, 6)。

所以纸带的分数为(1 + 5)*(5 + 2) + (4 + 6)*(2 + 2) = 42 + 40 = 82。

【数据说明】

对于第 1 组至第 2 组数据, 1 ≤ n ≤ 100, 1 ≤ m ≤ 5;

对于第 3 组至第 4 组数据, 1 ≤ n ≤ 3000, 1 ≤ m ≤ 100;

对于第 5 组至第 6 组数据, 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000,且不存在出现次数超过 20 的颜色;

对 于 全 部 10 组 数 据 , 1 ≤ n ≤ 100000, 1 ≤ m ≤ 100000, 1 ≤ color_i ≤ m,1≤number_i≤100000

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

解析:

考点:数学推导

解法一:穷举(时间超限)

参考代码:

#include <bits/stdc++.h>
using namespace std;
/*
 三元组:格子编号是等差数列
 第一格和第三格颜色相同
 (x+z)*(number_x+number_z) 
*/
int num[100100],color[100100];
int s,i,j,n,m;
int main(){
	cin>>n>>m;//m:颜色种类
	for(i = 1;i <= n;i++){
		cin>>num[i];
	}
	for(i = 1;i <= n;i++){
		cin>>color[i];
	} 
	//颜色相同,奇偶性相同的格子,就满足条件,可以计算
	//遍历所有可能的格子编号为等差数列的格子
	//遍历第 1 个格子
	for(i = 1;i < n;i++){
		//遍历第 3 个格子
		for(j = i + 2;j <= n;j = j + 2){
			if(color[i] == color[j]){
				s = s + (i + j) * (num[i] + num[j]);
				s = s % 10007;
			}
		}
	} 
	cout<<s;
	return 0;
}
解法二:数学推导
1 )三元组的值只和 xz 有关,且 xz 必定奇偶性相同,颜色相同
如果有三个数 x y z 是三元组,则一定满足 y - x = z - y,也就是必定是等差数列。
上述算式简化可得 2 * y = x + z ,也就是 x + z 是偶数, 那么 x z 必定同为奇 数,或者同为偶数
通过上述分析得知,本题和 y 没关系,只需要计算 x z 就行了,但如果穷举 x 和 z 的值,时间任然会超限。
利用三元组的第 2 个条件 color x = color z ,也就是 x z 颜色也必定相同。那么就有了 2 个条件: x z 必定奇偶相同, x z 必定颜色值相同
利用分组思想: 把相同颜色分为一组,在每个颜色中按下标的奇偶分组,这样在同一组 内的数值就满足三元组的规律。
2 )推导计算规律
假设有满足条件的分组对应额总和如下:
举例说明:
6 2
下标: 1 2 3 4 5 6
数值: 5 5 3 2 2 2
颜色: 2 2 1 1 2 1
经统计得知:
count[ 颜色 2][ 奇数下标] = 2         count[颜色 2][ 偶数下标 ] = 1
sum[ 颜色 2][ 奇数下标] = 7           sum[颜色 2][ 偶数下标 ] = 5
count[ 颜色 1][ 奇数下标] = 1         count[颜色 1][ 偶数下标 ] = 2
sum[ 颜色 1][ 奇数下标 ] = 3           sum[ 颜色 1][ 偶数下标 ] = 4
时间复杂度是 O(n)
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10,MOD = 10007;
int num[N],color[N];
//将所有的数据按照颜色分组、再按下标的奇偶性分组
//求对应分组的数有几个、对应分组的数值和是多少
int cnt[N][2],sum[N][2];
int n,m,res = 0;
int main(){
	 scanf("%d%d",&n,&m);
	 //读入 n 个数的数值
	 for(int i = 1;i <= n;i++) scanf("%d",&num[i]);
	 //读入颜色
	 for(int i = 1;i <= n;i++){
		 scanf("%d",&color[i]);
		 //分组统计
		 cnt[color[i]][i%2]++;
		 sum[color[i]][i%2]=(sum[color[i]][i%2]+num[i])%MOD;
	 }
	 //计算
	 for(int i = 1;i <= n;i++){
	 	res = (res + i * (sum[color[i]][i%2]+(cnt[color[i]][i%2]-2)*num[i] % MOD)) % MOD;
	 }
	 printf("%d",res);
	return 0;
}

另一种写法:

#include <bits/stdc++.h>

using namespace std;

int n, m;
int num[100005], color[100005];
int a1[100005][2], a2[100005][2], a3[100005][2], a4[100005][2];
const int p = 10007;

int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1;i <= n; i++) {
        scanf("%d", &num[i]);
    }
    for(int i = 1;i <= n; i++) {
        scanf("%d", &color[i]);
    }
    int ans = 0;
    for(int i = 1;i <= n; i++) {
        int y = i % 2;
        ans += a1[color[i]][y]; ans %= p;
        ans += a2[color[i]][y] * num[i]; ans %= p;
        ans += a3[color[i]][y] * i; ans %= p;
        ans += a4[color[i]][y] * i % p * num[i] % p; ans %= p;
        a1[color[i]][y] += 1ll * i * num[i] % p;
        a2[color[i]][y] += i; a2[color[i]][y] %= p;
        a3[color[i]][y] += num[i]; a3[color[i]][y] %= p;
        ++a4[color[i]][y]; a4[color[i]][y] %= p;
    }
    printf("%d", ans);
}

2015年NOIP普及组T4- 推销员

题目描述

阿明是一名推销员,他奉命到螺丝街推销他们公司的产品。螺丝街是一条死胡同,出口与入口是同一个,街道的一侧是围墙,另一侧是住户。螺丝街一共有N家住户,第i家住户到入口的距离为Si米。由于同一栋房子里可以有多家住户,所以可能有多家住户与入口的距离相等。阿明会从入口进入,依次向螺丝街的X家住户推销产品,然后再原路走出去。

阿明每走1米就会积累1点疲劳值,向第i家住户推销产品会积累Ai点疲劳值。阿明是工作狂,他想知道,对于不同的X,在不走多余的路的前提下,他最多可以积累多少点疲劳值。

输入格式

第一行有一个正整数N,表示螺丝街住户的数量。

接下来的一行有N个正整数,其中第i个整数Si表示第i家住户到入口的距离。数据保证S1≤S2≤…≤Sn<10^8。

接下来的一行有N个正整数,其中第i个整数Ai表示向第i户住户推销产品会积累的疲劳值。数据保证Ai<10^3。

输出格式

输出N行,每行一个正整数,第i行整数表示当X=i时,阿明最多积累的疲劳值。

输入输出样例

输入样例1:
5
1 2 3 4 5
1 2 3 4 5
输出样例1:
15
19
22
24
25
输入样例2:
5
1 2 2 4 5
5 4 3 4 1
输出样例2:
12
17
21
24
27

说明

【输入输出样例1说明】

X=1:向住户5推销,往返走路的疲劳值为5+5,推销的疲劳值为5,总疲劳值为15。

X=2:向住户4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为4+5,总疲劳值为5+5+4+5=19。

X=3:向住户3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值3+4+5,总疲劳值为5+5+3+4+5=22。

X=4:向住户2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值2+3+4+5,总疲劳值5+5+2+3+4+5=24。

X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值1+2+3+4+5,总疲劳值5+5+1+2+3+4+5=25。

【输入输出样例2说明】

X=1:向住户4推销,往返走路的疲劳值为4+4,推销的疲劳值为4,总疲劳值4+4+4=12。

X=2:向住户1、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4,总疲劳值4+4+5+4=17。

X=3:向住户1、2、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+4,总疲劳值4+4+5+4+4=21。

X=4:向住户1、2、3、4推销,往返走路的疲劳值为4+4,推销的疲劳值为5+4+3+4,总疲劳值4+4+5+4+3+4=24。或者向住户1、2、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+4+1,总疲劳值5+5+5+4+4+1=24。

X=5:向住户1、2、3、4、5推销,往返走路的疲劳值为5+5,推销的疲劳值为5+4+3+4+1,

总疲劳值5+5+5+4+3+4+1=27。

【数据说明】

对于20%的数据,1≤N≤20;

对于40%的数据,1≤N≤100;

对于60%的数据,1≤N≤1000;

对于100%的数据,1≤N≤100000。

解析:

考点:贪心、前缀最大值、前缀和,优先队列,单调队列

解法一:贪心,前缀最大值,前缀和

分析:

Si 1 2 2 4 5
Ai 5 4 3 2 1
1 )本质分析
假设选 x 家,本题计算结果 ans = max(max(si)*2+sum(ai))
假设 x = 2 ,选 2 家推销,如果选择 ai 值最大的 2 家,ans = 5 + 4 + 2 * 2 = 13
但是这并不是最优解,因为如果放弃掉其中较小的 ai=4 ,选一个更远的 si ,会得到更
优解,也就是 si * 2 + ai 的最大值 = 5 * 2 + 1 = 11 ,因此最优解 = 11 + 5 = 16
分析:是否可能放弃 ai 最大的 2 个解,通过更远的距离来获得更优解呢?答案是不能。
因为: si * 2 + ai 已经选了最大,再放弃最大的 ai ,不可能得到更大的和。
因此,本题的最优解:
f[x] = max( 最大的 x ai 的和 + 选出点的最大 si * 2, x-1 个最大 ai + 更远的
点中 si*2+ai 的最大值 )
2 )计算优化
采用暴力计算的时间复杂度为 O(n 2 ) ,不能拿满分。
优化点:
A 、对所有数据按照 ai 降序排序;
B 、求出 ai 的前缀和。
C 、求出 si 的前缀最大值。
D 、求出每个点向后看的 2 * si + ai 的最大值。
E 、套公式求解;
参考代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
struct node{
 int s,a;
}num[N];
int fa[N];//ai 的前缀和
int fs[N];//si 的前缀最大值
int fr[N];//从 i~n 之间 2*si+ai 的最大值
int n;
//排序规则:按推销疲劳值降序排序
bool cmp(node n1,node n2){
 return n1.a > n2.a;
}
int main() {
	 scanf("%d",&n);
	 for(int i = 1;i <= n;i++) scanf("%d",&num[i].s);
	 for(int i = 1;i <= n;i++) scanf("%d",&num[i].a);
	 //将所有的点按照推销疲劳值降序排序
	 sort(num+1,num+n+1,cmp);
	 //ai 的前缀和
	 for(int i = 1;i <= n;i++) fa[i] = fa[i-1] + num[i].a;
	 //si 的前缀最大值
	 for(int i = 1;i <= n;i++) fs[i] = max(fs[i-1],num[i].s);
	 //从 i~n 之间 2*si+ai 的最大值
	 for(int i = n;i >= 1;i--) fr[i] = max(fr[i+1],2*num[i].s+num[i].a);
	 //计算
	 for(int i = 1;i <= n;i++){
	 	printf("%d\n",max(fs[i]*2+fa[i],fa[i-1]+fr[i]));
	 }
	return 0;
}

解法二:优先队列和单调队列

思考

考虑x右边的两个住户i和j,假设选择x前,向i住户推销的总疲劳值>向j住户推销的总疲劳值,那么选x后呢?
①向i住户推销的总疲劳值>向j住户推销的疲劳值,即:s[i]*2+a[i]>s[j]*2+a[j]。
②选择x后,i和的推销疲劳值变为:(s[i]-s[x])*2+a[i]和(s[j]-s[x])*2+a[j]。 
③由于向i和j推销的总疲劳值都减少了s[x]*2;故i和j的推销疲劳值大小关系不变。

将住户的总推销疲劳值按照从大到小排序,x选择前后其右边住户的疲劳值大小关系不变。

右边最大值:从队首依次将位置≤x的元素删除(过期),删除后的队首元素即是右边最大值。

参考代码:

#include <iostream>
#include<queue>
#include<algorithm>    //优先队列维护下标 
using namespace std;
const int N=100005;
struct Node{
	int s,a,p;  //距离、疲劳值、位置 
} node[N];
//按照疲劳值从大到小排序
bool cmp(const Node& x,const Node& y){
	return x.s*2+x.a>y.s*2+y.a;
} 
int n,now,ans,s[N],a[N];
priority_queue<int> lq; //左边优先队列(疲劳值) 
queue<int> rq;  //右边单调队列   
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>node[i].s;
		node[i].p=i;
		s[i]=node[i].s;
	}
	for(int i=1;i<=n;i++){
		cin>>node[i].a;
		a[i]=node[i].a;
	}
	sort(node+1,node+1+n,cmp);
	//队列中存储元素的位置,即下标 
	for(int i=1;i<=n;i++) rq.push(node[i].p); //排序后的数据加入单调递减序列(结构体) 
	/*
	时间复杂度:此处虽然有循环嵌套,但实际上执行次数还是o(n)级别
	1.删除过期元素,最多执行n次,即把右边队列中的元素一个一个出队o(n) 
	2.将过期元素放入左边队列,最多就是把所有元素全部放入优先队列,也是n次o(n) 
	*/ 
	for(int i=1;i<=n;i++){    //每次选一家最大的,共n次 
		int mx=0,pre=now;   
		if(!lq.empty()) mx=lq.top();  //找出now左边最大的
		//删除过期元素(已经属于左半边部分的元素和上一次最大的元素(右边))
		while(!rq.empty()&&rq.front()<=now) rq.pop(); 
		//比较左右两边最值 ,更新now 
		if(!rq.empty()){  //右边最值 
			mx=max(mx,(s[rq.front()]-s[now])*2+a[rq.front()]); //右边最大的和左边比较 
		}
		if(!lq.empty()&&mx==lq.top()) lq.pop(); //左边大 
		else{  //右边大 
			now=rq.front();      //更新为右边较大者位置
			//rq.pop();               //删除已经使用的元素 
			for(int i=pre+1;i<now;i++) lq.push(a[i]) ;  //now左边入左边优先队列 
		}
		ans+=mx;
		cout<<ans<<endl; 
	} 
	return 0;
}

  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2004NOIP普及复赛数据是指在2004举办的NOIP普及复赛中所使用的题目和数据。NOIP是指全国青少信息学奥林匹克竞赛,旨在培养和选拔青少的计算机编程能力。 2004复赛中的数据包括了一系列的题目和测试用例。这些题目主要考察了参赛选手的编程基础和解题能力。每个题目都有一些特定的要求和限制条件。通过这些题目,考察选手对编程语言的掌握程度、算法的设计和实现能力、问题分析与解决的能力等。 复赛的数据往往是由工作人员根据题目要求设计和生成的。每个题目可能包含了多个测试用例,用于验证程序的正确性和性能。参赛选手需要编写程序解决这些题目,并保证程序能够正确地处理不同的测试用例。 在比赛中,选手需要根据题目描述和输入数据,编写相应的程序来求解问题。比赛规定了程序的运行时间和内存限制,选手需要在规定的时间和空间限制下编写高效的程序,并得到正确的输出结果。 通过参与2004NOIP普及复赛,选手们在实际的编程竞赛中得到了锻炼和提高。他们学会了如何拆解问题、设计算法、编写代码,并在紧张的比赛环境下克服困难,迅速解决问题。 综上所述,2004NOIP普及复赛数据是用于考察选手编程基础和解题能力的一系列题目和测试用例,选手需要根据题目描述和输入数据编写程序,以期在比赛中表现出色。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值