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

2016年NOIP普及组T1-买铅笔[pencil]

题目描述

P老师需要去商店买n支铅笔作为小朋友们参加NOIP的礼物。她发现商店一共有 3种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起 见,P老师决定只买同一种包装的铅笔。

商店不允许将铅笔的包装拆开,因此P老师可能需要购买超过n支铅笔才够给小朋 友们发礼物。

现在P老师想知道,在商店每种包装的数量都足够的情况下,要买够至少n支铅笔*最少*需要花费多少钱。

输入格式

从文件pencil.in中输入数据

输入的第一行包含一个正整数n,表示需要的铅笔数量。

接下来三行,每行用两个正整数描述一种包装的铅笔:其中第一个整数表示这种 包装内铅笔的数量,第二个整数表示这种包装的价格。

保证所有的7个数都是不超过10000的正整数。

输出格式

输出到文件pencil.out中

输出一行一个整数,表示P老师最少需要花费的钱。

输入输出样例

输入样例1:
57
2 2
50 30
30 27
输出样例1:
54
输入样例2:
9998
128 233
128 2333
128 666
输出样例2:
18407
输入样例3:
9999
101 1111
1 9999
1111 9999
输出样例3:
89991

说明

【样例1说明】

铅笔的三种包装分别是:

•2支装,价格为2;

•50支装,价格为30;

•30支装,价格为27。

P老师需要购买至少57支铅笔。

如果她选择购买第一种包装,那么她需要购买29份,共计2x29 = 58支,需要花 费的钱为2x29 = 58。

实际上,P老师会选择购买第三种包装,这样需要买2份。虽然最后买到的铅笔数 量更多了,为30x2 = 60支,但花费却减少为27 x2 = 54,比第一种少。

对于第二种包装,虽然每支铅笔的价格是最低的,但要够发必须买2份,实际的 花费达到了 30 x 2 = 60,因此P老师也不会选择。

所以最后输出的答案是54。

【子任务】

子任务会给出部分测试数据的特点。如果你在解决题目中遇到了困难,可以尝试 只解决一部分测试数据。

每个测试点的数据规模及特点如下表:

上表中“整倍数”的意义为:若为,表示对应数据所需要的铅笔数量n—定是每 种包装铅笔数量的整倍数(这意味着一定可以不用多买铅笔)。

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

解析

考点:循环,模拟

打擂台求最少的花费

参考代码

#include <bits/stdc++.h>
using namespace std;
int n;
int num,price;//每种包装的数量和价格
int ans = INT_MAX;
int main(){
	cin>>n;
	int t;
	//读入 3 种包装的数量和价格
	for(int i = 1;i <= 3;i++){
		cin>>num>>price;
		if(n % num == 0){
			t = n / num * price;
		}else{
			t = (n / num + 1) * price;
		}
		ans = min(ans,t);
	}
	cout<<ans;
	return 0;
}

2016年NOIP普及组T2-回文日期[date]

题目描述

在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。

牛牛习惯用8位数字表示一个日期,其中,前4位代表年份,接下来2位代表月 份,最后2位代表日期。显然:一个日期只有一种表示方法,而两个不同的日期的表 示方法不会相同。

牛牛认为,一个日期是回文的,当且仅当表示这个日期的8位数字是回文的。现 在,牛牛想知道:在他指定的两个日期之间包含这两个日期本身),有多少个真实存 在的日期是回文的。

【提示】

一个8位数字是回文的,当且仅当对于所有的i ( 1 <=i<= 8 )从左向右数的第i个 数字和第9-i个数字(即从右向左数的第i个数字)是相同的。

例如:

•对于2016年11月19日,用8位数字20161119表示,它不是回文的。

•对于2010年1月2日,用8位数字20100102表示,它是回文的。

•对于2010年10月2日,用8位数字20101002表示,它不是回文的。

每一年中都有12个月份:

其中,1、3、5、7、8、10、12月每个月有31天;4、6、9、11月每个月有30天;而对于2月,闰年时有29天,平年时有28天。

一个年份是闰年当且仅当它满足下列两种情况其中的一种:

1.这个年份是4的整数倍,但不是100的整数倍;

2.这个年份是400的整数倍。

例如:

•以下几个年份都是闰年:2000、2012、2016。

•以下几个年份是平年:1900、2011、2014。

输入格式

从文件date.in中读入数据

输入包括两行,每行包括一个8位数字。

第一行表示牛牛指定的起始日期。

第二行表示牛牛指定的终止日期。

保证date1和date2都是真实存在的日期,且年份部分一定为4位数字,且首位数字不为0。

保证date1—定不晚于date2。

输出格式

输出文件date.out中

输出一行,包含一个整数,表示在date1和date2之间,有多少个日期是回文的。

输入输出样例

输入样例1:
20110101
20111231
输出样例1:
1
输入样例2:
20000101 
20101231

输出样例2:
2

说明

【样例说明】

对于样例1,符合条件的日期是20111102。

对于样例2,符合条件的日期是20011002和20100102。

【数据范围】

对于60%的数据,满足date1 = date2。

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

解析

考点:枚举

思路:

循环所有的月和日的组合,如果月和日确定了

那么要构成回文,年,也一定是确定的

比如说:08  25 日, 那么年只能是 5280

日期: 52800825,只要改日期对应的这个 8 位数

在起始日期到终止日期的范围内,就是合法的回文日期

参考代码:

#include <bits/stdc++.h>
using namespace std;
int s,e;//起止日期
int m[13] = {0,31,29,31,30,31,30,31,31,30,31,30,31};
int ans = 0;
int main(){
	cin>>s>>e;
	int y;//代表年	
	int t;//代表完整的回文日期
	//穷举所有的月
	for(int i = 1;i <= 12;i++){
		//穷举每个月的日
		for(int j = 1;j <= m[i];j++){
			//0825
			y = j%10*1000+j/10*100+i%10*10+i/10;
			t = y * 10000 + i * 100 + j;//完整的回文日期
			//cout<<t<<endl;
			if(t >= s && t <= e){
				ans++;
			}
		}
	}
	cout<<ans;
	return 0;
}

2016年NOIP普及组T3-海港[port]

题目描述

小K是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客。

小K对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况;对于第i艘到达的船,他记录了这艘船到达的时间ti (单位:秒),船上的乘 客数ki,以及每名乘客的国籍 x(i,1), x(i,2),…,x(i,k);。

小K统计了n艘船的信息,希望你帮忙计算出以每一艘船到达时间为止的24小时(24小时=86400秒)内所有乘船到达的乘客来自多少个不同的国家。

形式化地讲,你需要计算n条信息。对于输出的第i条信息,你需要统计满足 ti - 86400 < tp <= ti的船只p,在所有的x(p,j)中,总共有多少个不同的数。

输入格式

从文件port.in中读入数据

第一行输入一个正整数n,表示小K统计了 n艘船的信息。

接下来n行,每行描述一艘船的信息:前两个整数ti和ki分别表示这艘船到达海港的时间和船上的乘客数量,接下来ki个整数x(i,j)表示船上乘客的国籍。

保证输入的ti是递增的,单位是秒;表示从小K第一次上班开始计时,这艘船在第 ti 秒到达海港。

输出格式

输出到文件port.out中

输出n行,第i行输出一个整数表示第i艘船到达后的统计信息。

输入输出样例

输入样例1:
3
1 4 4 1 2 2
2 2 2 3
10 1 3
输出样例1:
3
4
4
输入样例2:
4
1 4 1 2 2 3
3 2 2 3
86401 2 3 4
86402 1 5
输出样例2:
3
3
3
4

说明

【样例解释1】

第一艘船在第1秒到达海港,最近24小时到达的船是第一艘船,共有4个乘客, 分别是来自国家4,1,2,2,共来自3个不同的国家;

第二艘船在第2秒到达海港,最近24小时到达的船是第一艘船和第二艘船,共有 4 + 2 = 6个乘客,分别是来自国家4,1,2,2,2,3,共来自4个不同的国家;

第三艘船在第10秒到达海港,最近24小时到达的船是第一艘船、第二艘船和第 三艘船,共有4+ 2+1=7个乘客,分别是来自国家4,1,2,2,2,3,3,共来自4个不同 的国家。

【样例解释2】

第一艘船在第1秒到达海港,最近24小时到达的船是第一艘船,共有4个乘客,分别是来自国家1,2,2,3,共来自3个不同的国家。

第二艘船在第3秒到达海港,最近24小时到达的船是第一艘船和第二艘船,共有4+2=6个乘客,分别是来自国家1,2,2,3,2,3,共来自3个不同的国家。

第三艘船在第86401秒到达海港,最近24小时到达的船是第二艘船和第三艘船,共有2+2=4个乘客,分别是来自国家2,3,3,4,共来自3个不同的国家。

第四艘船在第86402秒到达海港,最近24小时到达的船是第二艘船、第三艘船和第四艘船,共有2+2+1=5个乘客,分别是来自国家2,3,3,4,5,共来自4个不同的国家。

【数据范围】

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

解析

考点:模拟,队列

思路

根据数据量可知,不能每读入一组数之后,循环所有读入过的人,逐个判断没有过期的 人中有多少种不同的国籍。

根据先到的人会先超时的原则, 可以想到用队列维护有效期范围内的人。

再加一个统计每个国籍人数的数组,就可以在每个人到之后,统计出对应国籍的人数, 在过期时, 从数组中将人数-1,这样每次维护国籍人数数组时,就可以统计出有多少种不同 的国籍了。

ti 船只到港时间, ki  i 艘船乘客数量

xi 乘客的国籍编号

问题:

每一艘船到达时间为止的 24 小时

所有乘船到达的乘客来自多少个不同的国家

1.使用队列来维护有效范围内的乘客;

2.使用数组计数法来统计不同国籍对应的人数;

参考代码:

#include<bits/stdc++.h>
using namespace std; 
struct node{
	int time,nation;//到港时间和国籍编号
};
queue<node> q;//存储有效时间范围的乘客信息
int cnt[100010];//统计不同编号国籍对应的人数
int n,t,k,x;
int main(){
	scanf("%d",&n);
	node d;
	int ans = 0;//表示有多少种不同的国籍
	while(n--){
		scanf("%d%d",&t,&k);//到港时间和人数
		//将超时的乘客, 从队列中弹出
		while(!q.empty()&&t-q.front().time>=86400){
			d = q.front();//队首元素
			q.pop();
			cnt[d.nation]--;//该国籍号对应的人数-1
			//如果这个国籍号没有人了,国籍种类数-1
			if(cnt[d.nation]==0) ans--;
		}
		//将当前船只的乘客入队, 统计国籍数量
		for(int i = 1;i <= k;i++){
			scanf("%d",&x);
			d.nation = x;
			d.time = t;
			q.push(d);
			//该乘客国籍对应的人数+1
			cnt[d.nation]++;
			//如果是第一次出现该编号的国籍,国籍种类数+1
			if(cnt[d.nation] == 1) ans++;
		}
		printf("%d\n",ans);
	}
	return 0;
}

2016年NOIP普及组T4-魔法阵[magic]

题目描述

六十年一次的魔法战争就要开始了,大魔法师准备从附近的魔法场中汲取魔法能量。

大魔法师有m个魔法物品,编号分别为1,2,...,m。每个物品具有一个魔法值,我们用Xi表示编号为i的物品的魔法值。每个魔法值Xi是不超过n的正整数,可能有多个物品的魔法值相同。

大魔法师认为,当且仅当四个编号为a,b,c,d的魔法物品满足时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的A物品,B物品,C物品,D物品。

现在,大魔法师想要知道,对于每个魔法物品,作为某个魔法阵的A物品出现的次数,作为B物品的次数,作为C物品的次数,和作为D物品的次数。

输入格式

从文件magic.in中读入数据

第一行包含两个空格隔开的正整数n和m

接下来m行,每行一个正整数,第i+1行的正整数表示Xi,即编号为i的物品的魔法值

每个Xi是分别在合法范围内等概率随机生成的

输出格式

输出到文件magic.out中

共输出m行,每行四个整数。第i行的四个整数依次表示编号为i的物品作 为A,B,C,D物品分别出现的次数。

保证标准输出中的每个数都不会超过10^9。

每行相邻的两个数之间用恰好一个空格隔开。

输入输出样例

输入样例1:
30 8
1
24
7
28
5
29
26
24
输出样例1:
4 0 0 0
0 0 1 0
0 2 0 0
0 0 1 1
1 3 0 0
0 0 0 2
0 0 2 2
0 0 1 0
输入样例2:
15 15
1 
2 
3 
4 
5
6 
7 
8 
9
10
11
12
13
14
15
输出样例2:
5 0 0 0
4 0 0 0
3 5 0 0
2 4 0 0
1 3 0 0
0 2 0 0
0 1 0 0
0 0 0 0
0 0 0 0
0 0 1 0
0 0 2 1
0 0 3 2
0 0 4 3
0 0 5 4
0 0 0 5

说明

【样例解释1】

共有5个魔法阵,分别为:

物品1,3,7,6,其魔法值分别为1,7,26,29;

物品1,5,2,7,其魔法值分别为1,5,24,26;

物品1,5,7,4,其魔法值分别为1,5,26,28;

物品1,5,8,7,其魔法值分别为1,5,24,26;

物品5,3,4,6,其魔法值分别为5,7,28,29。

以物品5为例,它作为A物品出现了1次,作为B物品出现了3次,没有作为C物品或者D物品出现,所以这一行输出的四个数依次为1,3,0,0。

此外,如果我们将输出看作一个m行4列的矩阵,那么每一列上的m个数之和都应等于魔法阵的总数。所以,如果你的输出不满足这个性质,那么这个输出一定不正确。你可以通过这个性质在一定程度上检查你的输出的正确性。

【数据规模】

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

解析

考点:数学、乘法原理、加法原理、前缀和。

1、理解四个点之间的关系

 b-a=2(d-c) , b-a<(c-b)/3,假设 d-c=k

 b-a=2*k,c-b>6*k

举例理解: 例如题目所描述的样例数据 1,7,26,29 四个数满足题目所述的要求。

2、理解本题所述的方案求解数学原理

1)假设 k = 2,有如下数字。

19 作为第三个数字 c 出现的可能有: 1 52 62 种不同的可能。

21 作为第四个数字 d 出现的可能也是 2 种,原理同上。

备注: 如果 19  3 ,那么就有 3 * 2 = 6 种解法得到数字 d

2)如果有重复的数字, 可以用数组计数法(桶排序)统计出每个数字出现的次数。 比如: 假设 k = 1,统计结果如下

13 作为数字 c 出现的可能有: (2 * 0 + 1 * 3 + 0 * 2 + 3 * 4) * 1 = 15 

15 作为数字 d 出现的可能有: (2 * 0 + 1 * 3 + 0 * 2 + 3 * 4) * 2 = 30 

也就是:

f3[c] = sum(cnt[a] * cnt[b]) * cnt[d]

f4[d] = sum(cnt[a] * cnt[b]) * cnt[c]

由于 bc 之间只要满足:c - b > 6 * k,这样一来,越往左的 ab 数对越容易满 足题意,也就是可以从左向右求前缀和。

反过来枚举 ab 其中一个点时, cd 越往右越容易满足,因此从右往左求前缀和。

3)枚举的每个点的范围

枚举步长 k 可能的范围: 由图可知 1+2k+6k+k<n,因此: k 的范围[1,n/9)

枚举 d 出现的位置: [1+9*k+1,n],有了 d 的位置,c 的位置必定是 d-ka 的位置必 定是: d-9*k-1b 的位置必定是:a+2*k

枚举 a 出现的位置: [1,n-9*k-1],有了 a 的位置,b 的位置必定是 a+k*2d 的位置 必定是 a+9*k+1c 的位置必定是 d-k

按照该枚举方式,本题时间复杂度 =n/9*n= O(n^2/9)

#include <bits/stdc++.h>
using namespace std;
const int N = 15010,M = 40010;
int cnt[N];//统计每个数出现的次数
//计算每个数分别作为第 1 个数~第 4 个数的不同可能
int f1[N],f2[N],f3[N],f4[N];
int x[M];
int n,m;
int main(){
	scanf("%d%d",&n,&m);
	//读入 m 个数,统计每个数出现的次数
	for(int i = 1;i <= m;i++){
		scanf("%d",&x[i]);
		cnt[x[i]]++;
	}
	//枚举可能的步长
	int a,b,c,d;
	int sum;//前缀和
	for(int k = 1;1 + 9 * k < n;k++){
		sum = 0;
		//枚举 d 可能的范围
		for(d = 1 + 9 * k + 1;d <= n;d++){
			a = d - 9 * k - 1;
			b = a + 2 * k;
			c = d - k;
			sum += cnt[a] * cnt[b];
			f3[c] += sum * cnt[d];
			f4[d] += sum * cnt[c];
		}
		sum = 0;
		//枚举 a 可能的范围
		for(a = n - 9 * k - 1;a >= 1;a--){
			b = a + 2 * k;
			d = a + 9 * k + 1;
			c = d - k;
			sum += cnt[c] * cnt[d];
			f1[a] += sum * cnt[b];
			f2[b] += sum * cnt[a];
		}
	}
	//输出结果
	for(int i = 1;i <= m;i++){
		printf("%d %d %d %d\n",f1[x[i]],f2[x[i]],f3[x[i]],f4[x[i]]);
	}
	return 0;
}

写法二:

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

int n,m;
int a[40001];
int f[5][15001]/*作为第i个物品时,魔法值为j的次数*/;
int d[15001]/*魔法值为i的物品个数*/;
int sum=0,cnt=0;

int main(){
    memset(f,0,sizeof(f));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&a[i]);
        d[a[i]]++;
    }
    for(int t=1;t*9+2<=n;t++){
        sum=0;cnt=0;
        for(int dd=9*t+2;dd<=n;dd++){
            int cc=dd-t;//第三个物品
            int bb=cc-6*t-1;//第二个物品
            int aa=bb-2*t;//第一个物品
            sum+=d[aa]*d[bb];//递推计算前缀和
            f[3][cc]+=sum*d[dd];//魔法值为d的个数可能不是/不止1个
            f[4][dd]+=sum*d[cc];//同理
        }
        for(int aa=n-9*t-1;aa>=1;aa--){
            int bb=aa+2*t;//第二个物品
            int cc=bb+6*t+1;//第三个物品
            int dd=cc+t;//第四个物品
            cnt+=d[cc]*d[dd];
            f[1][aa]+=cnt*d[bb];
            f[2][bb]+=cnt*d[aa];
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=1;j<=4;j++){
            printf("%d ",f[j][a[i]]);
        }
        printf("\n");
    }
    return 0;
}

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值