noip2016普及组复赛


2017.7.31 NOIP2016普及组复赛


第一题:

题目: 买铅笔
codevs题号:5622
时间限制: 1 s
空间限制: 256000 KB

题目描述 Description

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

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

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

输入描述 Input Description

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

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

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

输出描述 Output Description

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

57
2 2
50 30
30 27
样例输出 Sample Output

54
数据范围及提示 Data Size & Hint

这里写图片描述

题解:
这题因为只有三种铅笔,且题目说了只买同一种包装的铅笔,所以直接求出三种所需要的钱,取最小值就好了。比较麻烦的是在求每种铅笔的花费的钱时,要注意用需要的铅笔数量除以某种铅笔的的数量以求要买的数量时,判断一下能否除尽,不能就加上一。

上代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct hh
{
    int number;//数量 
    int price;//价格 
}a[10010];
long long n,number,price;
int main()
{
    cin>>n;
    for(int i=1;i<=3;i++)
    {
        scanf("%d%d",&a[i].number,&a[i].price);
    }
    int h[4];//保存花费的金额 
    for(int i=1;i<=3;i++)
    {
        if(n%a[i].number==0)
        {
            h[i]=(n/a[i].number)*a[i].price;//能整除就直接乘得出结果 
        }
        else 
        {
            h[i]=((n/a[i].number)+1)*a[i].price;//不能整除就想加上一再乘得结果 
        }
    }
    cout<<min(h[1],min(h[2],h[3]));//在三个中选最小的输出 
    fclose(stdin);
    fclose(stdout);
    return 0;
}

第二题

回文日期
codevs题号: 5623
时间限制: 1 s
空间限制: 256000 KB

题目描述 Description

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

牛牛习惯用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。

输入描述 Input Description

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

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

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

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

保证date1 —定不晚于date2。

输出描述 Output Description

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

20110101
20111231
样例输出 Sample Output

1
数据范围及提示 Data Size & Hint

【样例说明】

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

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

【子任务】

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

题解:
首先,题目说了对于60%的数据,满足date1 = date2。可以直接判断date1是否是回文日期,这样可以有60分。接下来说100分的。这题比较麻烦的是有几个循环的判断条件有点麻烦。首先,第一层循环是年份,第二层是月份,第三层循环是日期。年份没什么好说的,月份,首先是从date1的月份开始,如果年份等于date2的年份,那循环条件就设为小于等于date2的月份。然日期呢,要看月份,1,3,5,7,8,10,12月份条件设为31,如果如果是4,6,9,11月份就设为30,二月份要特判一下,闰年设为29,平年设为27。具体看代码。

#include<iostream>
#include<cstdio>
using namespace std;
int date1,date2,h=0;
void determine(int node)//判断一个八位数是否是回文数 
{
    int number[10];
    for(int i=1;i<=8;i++)
    {
        number[i]=node%10;
        node=node/10;
    }
    if(number[1]==number[8])
    if(number[2]==number[7])
    if(number[3]==number[6])
    if(number[4]==number[5])
    h++;
}
int bit4(int node)//取年份 
{
    int h=0,n=1;
    for(int i=1;i<=4;i++) node=node/10;
    for(int i=1;i<=4;i++)
    {
        h=(node%10)*n+h;
        node/=10;
        n*=10;
    }
    return h;
}
int bit2(int node)//取月份 
{
    int h=0,n=1;
    for(int i=1;i<=2;i++) node=node/10; 
    for(int i=1;i<=2;i++)
    {
        h=(node%10)*n+h;
        node/=10;
        n*=10;
    }
    return h;
}
int bit22(int node)//取日数(xx年xx月xx日) 
{
    int h=0,n=1;
        for(int i=1;i<=2;i++)
    {
        h=(node%10)*n+h;
        node/=10;
        n*=10;
    }
    return h;
}
int nnnn(int i)//判断平闰年 
{
    if(i%4==0&&i%100!=0) return 1;
    if(i%400==0) return 1;
    return 0;
}
int main()
{
    cin>>date1>>date2;
    int j=bit2(date1),k=bit22(date1);
    for(int i=bit4(date1);i<=bit4(date2);i++)//年份 
    {
        int y; 
        if(i==bit4(date2))
        y=bit2(date2);//确定月份循环的条件 
        else y=12;

        for(;j<=y;j++)//月份循环 
        {
            int r;
            if(j==1||j==3||j==5||j==7||j==8||j==12||j==10)//确定日的循环条件 
            r=31;
            else if(j==4||j==6||j==9||j==11)
            r=30;
            else if(j==2)//特判二月份, 
            {
                if(nnnn(j)==1) r=29;
                else r=28;
            }
            for(;k<=r;k++)//日的循环 
            {
                determine(i*10000+j*100+k);//判断是否是回文数(i是年份,j是月份,k是日数 ,   就是i年j月k日) 
            }
            k=1;
        }
        j=1;
    }
    cout<<h;

    return 0;
}

第三题:

5621 海港

时间限制: 1 s
空间限制: 256000 KB
题目等级 : 钻石 Diamond

题目描述 Description

小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)中,总共有多少个不同的数。

输入描述 Input Description

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

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

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

保证这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
其中这里写图片描述表示所有的ki的和。

输出描述 Output Description

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

3
1 4 4 1 2 2
2 2 2 3

10 1 3
样例输出 Sample Output

3
4
4
数据范围及提示 Data Size & Hint

这里写图片描述

题解:一开始我想的是用一个二维数组保存信息,但一想会爆的,这是看到题目提供的是k的总和,而不是每艘船的k的值,所以这题用队列解决。创一个结构体,保存到岸时间和国籍。每个人依次压进队列中。然后求总的国籍数,把不符合条件的人挤出队列,如果挤出后,这个国家没人在了,就把总的国籍数减一。具体看代码。

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
int n,x,k,s=0;
struct hh//请忽略这个诡异的结构体名称 
{
    int x,time;//x是国籍,time是这个人所在的船到岸的时间 
}y,o;
queue<hh>q;
int pd[100010];//一个记录i国有几个人的数组 
int main()
{
    memset(pd,0,sizeof(pd));
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>y.time>>k;//输入到岸时间和人这艘船的人数(反正都是一艘船,时间都一样) 
        for(int j=1;j<=k;j++)
        {
            cin>>y.x;//输入此人国籍 
            q.push(y);//把这个人的信息压入队列 
            if(pd[y.x]==0)//如果此前无这个国籍的人,就把总的国家数加上一 
            s++;
            pd[y.x]++;//这个国家的人数自然要加一啦 
        }
        o=q.front();//取出队首的人的信息 
        while(o.time<=y.time-86400)//如果这个人的时间距超过我们要查询的时间范围,就去掉 
        {
            pd[o.x]--;//这个国家的人数减一 
            if(pd[o.x]==0)//如果这个国家没人,总的国家数自然减一 
            s--;
            q.pop();//删去队首元素 
            o=q.front();//再一次取队首元素,接着比较 
        }
        cout<<s<<endl;//输出剩下的总国家数 
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

第四题:

题目:魔法阵
codevs题号:5624
时间限制: 1 s
空间限制: 256000 KB
题目等级 : 大师 Master

题目描述 Description

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

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

大魔法师认为,当且仅当四个编号为a,b,c,d的魔法物品满足xa ,xb,xc,xd数值依次增大,xb-xa=2(Xd-Xc),并且xb-xa<(xc-xb)/3时,这四个魔法物品形成了一个魔法阵,他称这四个魔法物品分别为这个魔法阵的A物品,B物品,C物品,D物品。

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

输入描述 Input Description

输入文件的第一行包含两个空格隔开的正整数n和m。

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

保证1<=n<=15000, 1<=m<=40000,1<=xi<=n,每个xi是分别在合法范围内等概率随机生成的。

输出描述 Output Description

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

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

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

样例输入 Sample Input

15 15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

样例输出 Sample Output

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
数据范围及提示 Data Size & Hint

这里写图片描述

题解:

首先看条件:
1: xa,xb,xc,xd数值依次递增.
2: xb-xa=2(xd-xc)
3: xb-xa<(xc-xb)/3;
4:n<=15000,m<=40000。
所以如果你只写四重循环的话,是用不到n的。但请记住,没有白给的条件,没有没用的数据。
四重循环代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,v;
long long int  f[20000];
long long int aa[20000],bb[20000],cc[20000],dd[20000];
void pop(int a,int b,int c,int d)
{
    if((f[b]-f[a])==2*(f[d]-f[c]))
    if(3*(f[b]-f[a])<f[c]-f[b])
    {
        aa[a]++;
        bb[b]++;
        cc[c]++;
        dd[d]++;
    }
}
int main()
{
    freopen("magic.in","r",stdin);
    freopen("magic.out","w",stdout);
    memset(aa,0,sizeof(aa));
    memset(bb,0,sizeof(bb));
    memset(cc,0,sizeof(cc));
    memset(dd,0,sizeof(dd));
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&f[i]);
    }
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=m;j++)
        {
            for(int k=1;k<=m;k++)
            {
                for(int l=1;l<=m;l++)
                {
                    if((f[i]<f[j]&&f[j]<f[k])&&f[k]<f[l])
                    {
                        pop(i,j,k,l);
                    }
                }
            }
        }
    }
    for(int i=1;i<=m;i++)
    {
        cout<<aa[i]<<" "<<bb[i]<<" "<<cc[i]<<" "<<dd[i]<<endl;
    }
    return 0;
}

现在想想怎么拿满分,首先找出A,b,C,D之间的数学关系。
我们可以把这m个点画在一个数轴上;

xb-xa=2(xd-xc)可以变为AB=2*CD
(ps:AB为xb-xa的值)
xb-xa<(xc-xb)/3;可以变为3AB小于CD
最终得出:
AB=2*CD,BC>6*CD,AD>9*CD
所以如果ABCD是魔法阵的四个物品,那么根据题目,它们一定满足AB=2*CD,BC>6*CD,AD>9*CD。那么我们只需要确定D,就可以确定C点,然后再找AB。
上一下自己画的小图,比较丑啊。
x=CD,i为D点的魔法值
这里写图片描述

上代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
 int n,m,f[20010],v;
 int aaa[40010];
 int a[20010],b[20010],c[20010],d[20010];
int main()
{
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
    memset(d,0,sizeof(d));
    memset(f,0,sizeof(f));
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        scanf("%lld",&v);
        aaa[i]=v;//保存物品的顺序,以便输出 
        f[v]++;//像装入桶一样,保存每个魔法值有多少个 
    }
    for(int x=1;9*x+1<=n;x++)
    {
        long long int h=0;
        for(int dx=9*x+2;dx<=n;dx++)
        {
             h+=(f[dx-9*x-1]*f[dx-7*x-1]);//xa*xb,就是符合情况的a,b情况总和 
            d[dx]+=f[dx-x]*h;//d的情况数等于a,b的情况数乘以固定的才点的情况属 
            c[dx-x]+=h*f[dx];//同理 
        }
        h=0;
        for(int ax=n-9*x-1;ax>=1;ax--)
        {
             h+=(f[ax+8*x+1]*f[ax+9*x+1]);//同理 
             a[ax]+=f[ax+2*x]*h;
             b[ax+2*x]+=h*f[ax];
        }
    }
    for(int i=1;i<=m;i++)//按顺序输出 
    {
        cout<<a[aaa[i]]<<" "<<b[aaa[i]]<<" "<<c[aaa[i]]<<" "<<d[aaa[i]]<<endl;
    }
    return 0;
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值