• 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
    ",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
    ",b[a[i]]-1);
        return 0;
    }

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

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

  • 相关阅读:
    洛谷 P2896 [USACO08FEB]Eating Together S
    洛谷 P1564 膜拜
    洛谷 P1684 考验
    洛谷 P2031 脑力达人之分割字串
    洛谷 P2725 邮票 Stamps
    洛谷 P2904 [USACO08MAR]跨河River Crossing
    洛谷 P1929 迷之阶梯
    洛谷 P2375 [NOI2014]动物园
    谷歌浏览器禁止表单自动填充
    SQL数据同步之发布订阅
  • 原文地址:https://www.cnblogs.com/wendcn/p/12620934.html
Copyright © 2020-2023  润新知