2016普及组(复赛)

尝试水一波。。。

难度感觉还行。

T1.买铅笔

题目描述

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

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

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

输入输出格式

输入格式:

 

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

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

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

 

输出格式:

1个整数,表示P老师最少需要花费的钱。

思路:

  打卡签到题。贪心一波,5 min AC。

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<deque>
#include<set>
#include<map>
#include<vector>
#include<fstream>
using namespace std;
int n;
int a[3][3];
inline int read()
{
    char kr=0;
    char ls;
    for(;ls>'9'||ls<'0';kr=ls,ls=getchar());
    int xs=0;
    for(;ls>='0'&&ls<='9';ls=getchar())
    {
        xs=xs*10+ls-48;
    }
    if(kr=='-') xs=0-xs;
    return xs;
}
int main()
{
    n=read();
    for(int i=1;i<=3;i++)
    {
        for(int j=1;j<3;j++)
        {
            a[i][j]=read();
        }
    }
    int minn=99999999,cnt;bool ok;
    for(int i=1;i<=3;i++)
    {
        ok=0;
        if(n%a[i][1]) ok=1;
        cnt=n/a[i][1]+ok;
        minn=min(minn,a[i][2]*cnt);
    } 
    printf("%d\n",minn);
return 0;
}

注:minn要尽量开大。

T2.回文日期

题目描述

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

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

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

一个8位数字是回文的,当且仅当对于所有的 i (1i8)从左向右数的第i个 数字和第9i个数字(即从右向左数的第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

输入输出格式

输入格式:

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

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

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

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

保证date 1—定不晚于dat2。

输出格式:

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

思路:

  也是水题一道,只要从date 1 到data 2枚举年月,判断是否回文,且是否位于指定的时间内,统计是回文的年份。(暴力枚举)


第一遍蒟丑的代码:

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<deque>
#include<set>
#include<map>
#include<vector>
#include<fstream>
using namespace std;
string s1;
string s2;
int t1[8];
int t2[8];
inline int read()
{
    char kr=0;
    char ls;
    for(;ls>'9'||ls<'0';kr=ls,ls=getchar());
    int xs=0;
    for(;ls>='0'&&ls<='9';ls=getchar())
    {
        xs=xs*10+ls-48;
    }
    if(kr=='-') xs=0-xs;
    return xs;
}
int main()
{
    int start=0,end=0;
    cin>>s1>>s2;
    for(int i=0;i<8;i++)
    {
        t1[i]=s1[i]-48;
        t2[i]=s2[i]-48;
    }
    for(int i=0;i<4;i++)
    {
        start=start*10+t1[i];
        end=end*10+t2[i];
    }
    int pd1=0,pd2=0;
    for(int j=4;j<8;j++)
    {
        pd1=pd1*10+t1[j];
        pd2=pd2*10+t2[j];
    }
    int tot=0;
    int yue=0,ri=0;
    for(int i=start;i<=end;i++)
    {
        yue=0;ri=0;
        int pj=i%10;//第四位 
        int pi=(i/10)%100;//第三位 
        int po=(i/100)%10;//第二位 
        int pu=i/1000;//第一位
        if(pj>1) continue;
        yue=pj/10+pi;
        ri=po*10+pu;
        if(i==start)
        {
            if(yue*100+ri<pd1)
                continue;
        }
        if(i==end)
        {
            if(yue*100+ri>pd2)
                continue;
        }
        if(yue!=2)
        {
            if(pj==0)
            {
                tot++;
                continue;
            }
            if(pj>0)
            {
                if(pi>=0&&pi<=2)
                {
                    tot++;
                    continue;
                }
            }
        }
        if(yue==2)
        {
            bool ok=0;
            if(!(start%4) && start%100)
            {
                ok=1;
            }            
            else if(!(start%100))
            {
                if(!(start%400))
                    ok=1;
            }
            if(ok)
            {
                if(ri>0&&ri<30)
                {
                    tot++;
                    continue;
                }
            }
            else if(!ok)
            {
                if(ri>0&&ri<29)
                {
                    tot++;
                    continue;
                }
            }
        }
    }
    printf("%d\n",tot);
return 0;
}

优化后的代码:

#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#include<stack>
#include<vector> 
using namespace std;
int s[13]={0,31,29,31,30,31,30,31,31,30,31,30,31};
int main()
{
    int ans=0;
    string s1,s2;
    int start=0,end=0;
    cin>>s1>>s2;
    for(int i=0;i<4;i++)
    {
        start=10*start+s1[i]-48;
        end=10*end+s2[i]-48;
    }
    for(int i=1;i<=12;i++)
    {
        for(int j=1;j<=s[i];j++)
        {
            int nx=i*100+j;
            int pi=nx%10;
            int po=(nx/10)%10;
            int pu=(nx/100)%10;
            int pv=nx/1000;
            int tot=pi*1000+po*100+pu*10+pv;
            if(tot>=start&&tot<=end) ans++;
        }
    }
    printf("%d\n",ans);
return 0;
}

T3.海港

题目描述

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

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

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

形式化地讲,你需要计算n条信息。对于输出的第i条信息,你需要统计满足ti86400<tpti的船只p,在所有的xp,j中,总共有多少个不同的数。

输入输出格式

输入格式:

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

接下来n行,每行描述一艘船的信息:前两个整数tiki分别表示这艘船到达海港的时间和船上的乘客数量,接下来ki个整数,表示船上乘客的国籍xi,1,xi,2...xi,k

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

保证 1n105,ki3105 ,1xi,j105, 1t(i1)ti109

其中ki表示所有的ki的和。

输出格式:

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

思路:

  ①我个人的思路是开个结构体,存每一个人的进港的时间和所属的国籍,用一个变量nn记录一天中最早进港的那艘船的编号。(从0s开始标记船,每进一艘,就把这艘船打上标记(++cnt))。船进港后,统计人头数(国籍,时间)存入结构体,这一天内最早的那艘船开始暴力统计(过时的船就把在那艘船上的人的信息全部清空,更新nn),直接输出。(相当暴力)

  ②正解:也是记录每个人的进港时间和国籍(跟我的差不多),正解唯一精妙之处在于,在统计国籍的时候用了个叫队列的东西。没有那么暴力。(思路还是一样的)

暴力代码(70分):

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<deque>
#include<set>
#include<map>
#include<vector>
#include<fstream>
using namespace std;
#define maxn 300007
#define day 86399
struct hh
{
    int time,gg;
}t[maxn];
int n,cnt=0;
inline int read()
{
    char kr=0;
    char ls;
    for(;ls>'9'||ls<'0';kr=ls,ls=getchar());
    int xs=0;
    for(;ls>='0'&&ls<='9';ls=getchar())
    {
        xs=xs*10+ls-48;
    }
    if(kr=='-') xs=0-xs;
    return xs;
}
int main()
{
    n=read();
    int kk,sx,lck,tot=0;
    int nn;
    int tt[maxn];
    for(int i=1;i<=n;i++)
    {
        kk=read();
        if(i==1) nn=1;
        sx=read();
        for(int j=0;j<sx;j++)
        {
            ++cnt;
            t[cnt].time=kk;
            t[cnt].gg=read();
        }
        tot=0;
        memset(tt,0,sizeof(tt));
        for(int j=nn;j<=cnt;j++)
        {
            if(t[j].time<(kk-day))
            {
                t[j].time=0;
                t[j].gg=0;
                nn=j+1;
                continue;
            }
            if(!tt[t[j].gg])
            {
                tt[t[j].gg]=1;
                tot++;
            }
        }
        cout<<tot<<endl;
    }
return 0;
}

正解:

#include <cstdio>
struct NOI
{
    int tm,ct;
}a[300005];
int b[100005];    //b[i]记录【二十四小时内】有多少个国籍为i的人
int n,k,tot,time,i,j,tmp;
int ans,head;
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;i++){
        scanf("%d%d",&time,&k);
        while(k--){
            scanf("%d",&tmp);   //读入人的同时,扔进队列
            a[++tot].tm=time;
            a[tot].ct=tmp;
            if(b[tmp]==0)ans++;   //新的品种,ans++
            b[tmp]++;
        }
        while(time-a[head].tm>=86400){    //二十四小时前的,扔出队列
            tmp=a[head].ct;
            b[tmp]--;
            if(b[tmp]==0)
                ans--;     //品种灭绝,ans--
            head=head+1;
        }
        printf("%d\n",ans);  //ans已经统计好种类数了
    }
    return 0;
}

T4.魔法阵

题目描述

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

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

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

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

输入输出格式

输入格式:

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

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

保证1n15000,1m40000,1Xin。每个Xi是分别在合法范围内等概率随机生成的。

输出格式:

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

保证标准输出中的每个数都不会超过109。每行相邻的两个数之间用恰好一个空格隔开。

思路:

  本题的题目这么显然,若是想要骗分,手打一个N4的暴力,35分就有了。

  但是,如果要AC就需要动点脑子——

  先观察题目里的三个条件:

    Xa<Xb<Xc<Xd

    Xb-Xa=2(Xd-Xc)

    3(Xb-Xa)<(Xc-Xb)

  第一个条件告诉我们,选择的四元组必须是一个严格递增的。

  对于后两个条件,先转换成数学模型,不难发现其中Xd-Xc是最小的单位,所以我们可以令t=Xd-Xc,则整理式子后可以写成:

    Xd-Xc=t

    Xb-Xa=2t

    Xc-Xb>6t

  到现在为止我们找到了一组关于a,b,c,d的连续的关系,于是就可以计算了。

  因为题目里的数字的范围比数字的数量要少,所以用桶排序,根据乘法原理,一个数要是出现了多次直接乘上就好了。

  然后我们枚举长度tt,在分别枚举a,d的位置,当枚举了t和任意一个点的位置以后,其余的点根据上面的连续的关系都可以直接确定。

  不难发现有一部分的计算是重复的,所以可以用一个变量记录一下前缀和,每次跟新以后直接加在后面。(就这样推理一下,就把N4的超级暴力优化为N2的AC)

暴力代码(35分):

#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<deque>
#include<set>
#include<map>
#include<vector>
#include<fstream>
using namespace std;
#define maxn 45000
int n,m;
int sum[maxn];
struct hh
{
    int a,b,c,d;
}t[maxn];
inline int read()
{
    char kr=0;
    char ls;
    for(;ls>'9'||ls<'0';kr=ls,ls=getchar());
    int xs=0;
    for(;ls>='0'&&ls<='9';ls=getchar())
    {
        xs=xs*10+ls-48;
    }
    if(kr=='-') xs=0-xs;
    return xs;
}
int main()
{
    m=read();n=read();
    for(int i=1;i<=n;i++)
    {
        sum[i]=read();
    }
    for(int i=1;i<=n;i++)//a
    {
        for(int j=1;j<=n;j++)//b
        {
            if(j==i) continue;
            for(int k=1;k<=n;k++)//c
            {
                if(k==j||k==i) continue;
                for(int l=1;l<=n;l++)//d
                {
                    if(l==i||l==j||l==k) continue;
                    if(sum[i]>=sum[j] || sum[i]>=sum[k] ||sum[i]>=sum[l] ||sum[j]>sum[k] ||sum[j]>sum[l] || sum[k]>sum[l]) continue;
                    if(sum[j]-sum[i]==2*(sum[l]-sum[k]))
                    {
                        if((sum[j]-sum[i])*3<sum[k]-sum[j])
                        {
                            ++t[i].a;
                            ++t[j].b;
                            ++t[k].c;
                            ++t[l].d;
                        }
                    }
                }
            }
        }
    }
    for(int i=1;i<=n;i++)
    {
        printf("%d %d %d %d\n",t[i].a,t[i].b,t[i].c,t[i].d);
    }
return 0;
}

正解代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,m;
int val[40010],num[15010];
int a[15010],b[15010],c[15010],d[15010];
int main()
{ scanf(
"%d%d",&n,&m); for(int i=1;i<=m;i++)
  { scanf(
"%d",&val[i]); num[val[i]]++; } for(int i=1;i*9+1<=n;i++)
  {
int sum=0; for(int j=i*9+2;j<=n;j++)
    { sum
+=num[j-7*i-1]*num[j-9*i-1]; c[j-i]+=num[j]*sum; d[j]+=num[j-i]*sum; } sum=0; for(int j=n-i*9-1;j>=1;j--)
    { sum
+=num[j+i*9+1]*num[j+i*8+1]; a[j]+=num[j+2*i]*sum; b[j+2*i]+=num[j]*sum; } } for(int i=1;i<=m;i++)
  { printf(
"%d %d %d %d\n",a[val[i]],b[val[i]],c[val[i]],d[val[i]]); }
return 0
;
}

自测:

用时:3.5h+一节课。

score:100+100+70+35=305

传送门

 

posted @ 2018-09-13 20:09  落笔映惆怅丶  阅读(211)  评论(0编辑  收藏  举报