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 5、2 6,2 种不同的可能。
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]
由于 b、c 之间只要满足:c - b > 6 * k,这样一来,越往左的 a、b 数对越容易满 足题意,也就是可以从左向右求前缀和。
反过来枚举 ab 其中一个点时, cd 越往右越容易满足,因此从右往左求前缀和。
(3)枚举的每个点的范围
枚举步长 k 可能的范围: 由图可知 1+2k+6k+k<n,因此: k 的范围[1,n/9)
枚举 d 出现的位置: [1+9*k+1,n],有了 d 的位置,c 的位置必定是 d-k,a 的位置必 定是: d-9*k-1,b 的位置必定是:a+2*k。
枚举 a 出现的位置: [1,n-9*k-1],有了 a 的位置,b 的位置必定是 a+k*2,d 的位置 必定是 a+9*k+1,c 的位置必定是 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;
}