W3 school 菜鸟教程 我要自学网 信息学奥赛NOI 花哥的博客 不逼自己一把,怎知自己有多优秀——校长语录

1621:轻拍牛头

1621:轻拍牛头原题(嵌入代码可能效果不好,请点链接看原题)

【题目描述】
原题来自:USACO 2008 Dec. Silver

今天是贝茜的生日,为了庆祝自己的生日,贝茜邀你来玩一个游戏。

贝茜让 NN 头奶牛坐成一个圈。除了 11 号与 NN 号奶牛外,ii 号奶牛与 i−1i−1 号和 i+1i+1 号奶牛相邻,NN 号奶牛与 11 号奶牛相邻。农夫约翰用很多纸条装满了一个桶,每一张包含了一个 11106106 的数字。

接着每一头奶牛 i 从桶中取出一张纸条 Ai ,每头奶牛轮流走一圈,同时拍打所有「编号是 AiAi 的约数」的牛,然后走回到原来的位置。牛们希望你帮助他们确定,每一头奶牛需要拍打的牛。

【输入】
第一行包含一个整数 NN;

接下来第二到第 N+1N+1 行每行包含一个整数 AiAi 。

【输出】
第一到第 N 行,第 i 行的输出表示第 i 头奶牛要拍打的牛数量。

【输入样例】
5
2
1
2
3
4
【输出样例】
2
0
2
1
3
【提示】
数据范围与提示:

对于全部数据,1≤N≤1051≤N≤105
View Code

【题意说明】

  对这个题目的描述,我觉得有点描述不清。”每头奶牛轮流走一圈,同时拍打所有[编号是Ai的约数]的牛“这里的编号指向不明确,如果是最初的1-N编号,显然样例数据不符。因为2号奶牛拿的数字是1,那它至少应该拍1号奶牛,但样例输出是0。查看另一篇博文https://www.luogu.com.cn/problem/P2926,描述为”每头奶牛轮流走上一圈,同时拍打所有手上数字能整除在自己纸条上的数字的牛的头“,这个描述很清楚,也符合样例数据,以下内容均按后者理解。

【分析】

  这个题目看起来并不难,判断一个数是否是另一个数的约数取余就可以了,要计算拍牛的次数,加一个计数器就OK。所以轻松搞定代码。

#include<iostream>
using namespace std;
int const N=1e6+5;
int a[N],ans[N],n; 
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)//枚举每一头牛 
        for(int j=1;j<=n;j++)//其他牛的编号是否为当前牛编号的约数 
            if(a[i]%a[j]==0)ans[i]++;
    for(int i=1;i<=n;i++)cout<<ans[i]-1<<endl;//ans[i]-1是要减去本身 
    return 0;
}

 

  样例顺利通过,提交网站结果应该可以想到。这样都能通过是不是也太没水平了。简单算一个,对于百分之百的数据n<=10^5,一个双循环10^10,超时是必然的。

  在之前一个题1619:用到了筛选法可以优化时间复杂度,那我们也可以考虑用筛选法优化。我们不用把每一个编号都去验证,只把当前编号的倍数标记一次。那么每一头牛的编号由原来的验证其余牛的时间复杂度n可降为最大编号值/当前编号值。代码实现

//1621:轻拍牛头 
#include<iostream>
using namespace std;
int const N=1e6+5;
int a[N],b[N],n,c[N],maxn=0; 
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(maxn<a[i])maxn=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=a[i];j<=maxn;j+=a[i])
            b[j]++;
    }
    for(int i=1;i<=n;i++)
        cout<<b[a[i]]-1<<endl;
    return 0;
}

  再次提交,46分,其余超时。想想一下,这个题数据量达到10^5,输入和输出应该占时较大,所以cin/cout的效率让我们不得不放入考虑范围,换吧,scanf/printf的效率可以提高不少,在前面博文中也有提及,特别是1205题就是一个很好例子。得以下代码

//1621:轻拍牛头 
#include<cstdio>
int const N=1e6+5;
int a[N],b[N],n,maxn=0; 
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        if(maxn<a[i])maxn=a[i];
    }
    for(int i=1;i<=n;i++)
        for(int j=a[i];j<=maxn;j+=a[i])
            b[j]++;
    for(int i=1;i<=n;i++)
        printf("%d\n",b[a[i]]-1);
    return 0;
}

  再次提交,84分。这还能优化?必须啊,别人能过啊,那题肯定没问题啊。思考良久,想到一个地方:编号如果有重复,那我们验证时就会重复验证,特别编号又小,重复量再大一点,这一环节的效果更明显,再挤一挤吧。把重复项合并,只筛选一次就好。代码如下

//1621:轻拍牛头 
#include<cstdio>
int const N=1e6+5;
int a[N],b[N],c[N],d[N][2],cnt,n,maxn,x; 
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",a+i);
        c[a[i]]++;
        if(maxn<a[i])maxn=a[i];
    }
    for(int i=1;i<=maxn;i++)
        if(c[i])d[++cnt][0]=i,d[cnt][1]=c[i];
    for(int i=1;i<=cnt;i++)
        for(int j=d[i][0];j<=maxn;j+=d[i][0])
            b[j]+=d[i][1];
    for(int i=1;i<=n;i++)
        printf("%d\n",b[a[i]]-1);
    return 0;
}

  再次提交:100分。终于AC了!

如果大家有更好看法,欢迎评论!!!

posted @ 2020-04-02 16:56  耍人  阅读(558)  评论(0编辑  收藏  举报