1621:轻拍牛头原题(嵌入代码可能效果不好,请点链接看原题)
【题目描述】 原题来自:USACO 2008 Dec. Silver 今天是贝茜的生日,为了庆祝自己的生日,贝茜邀你来玩一个游戏。 贝茜让 NN 头奶牛坐成一个圈。除了 11 号与 NN 号奶牛外,ii 号奶牛与 i−1i−1 号和 i+1i+1 号奶牛相邻,NN 号奶牛与 11 号奶牛相邻。农夫约翰用很多纸条装满了一个桶,每一张包含了一个 11 到 106106 的数字。 接着每一头奶牛 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 。
【题意说明】
对这个题目的描述,我觉得有点描述不清。”每头奶牛轮流走一圈,同时拍打所有[编号是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了!
如果大家有更好看法,欢迎评论!!!