您的位置:首页 > 其它

洛谷1414 又是毕业季II

2017-06-21 21:27 204 查看
个人觉得这个题是一个不错的数论入门题。

首先显然你不能枚举选了哪些数,这样复杂度爆表。

迫不得已考虑枚举另一些东西,例如k个数的因数。

这是由于注意到题目的数字只有1e6的大小说明算法肯定和它有关系,否则直接出到1e9不就好了。

现在问题转化为你枚举k个数的公因数,然后判断这个数能否作为k个数的公因数。

于是第一个初步的算法模型:

首先枚举k,然后从大到小枚举d,d即这k个数的公因数。

然后枚举i=1~n,如果a[i]%d==0那么cnt++。

然后如果cnt>=k那么ans[k]=d。

记A为n个数中的最大数。

枚举k是O(n)的,枚举d是O(A)的,枚举i是O(n)的。算出来是1e16的算法。显然T的飞起。

考虑优化,事实上我们注意到a[1]~a
中有多少个数字是d的倍数是可以预处理的。

即我们可以枚举d,然后枚举i=1~n,如果a[i]%d==0那么cnt[d]++。

这样就可以用cnt数组代替上一次求cnt的过程。

这样算出来复杂度是O(nA)也就是1e11,很不幸还是T的飞起。

注意到无论是预处理还是计算答案都是O(nA)的所以两部分分开优化。

对于预处理部分,我们继续转化思路,即我们不是枚举d,然后计算cnt[d],

而是枚举i,考虑a[i]对cnt数组的贡献。

发现事实上,如果a[i]%d==0,那么就会有cnt[d]++。

所以可以枚举i,然后枚举a[i]的因数,并在cnt[a[i]的因数]位置++;

枚举a[i]是可以做到sqrt(a[i])的。

所以预处理复杂度降为O(n*sqrt(A))。大概是1e8,事实上由于并不是所有数字都=A所以常数比较小,原题大概0.01秒就跑的出来。

事实上枚举a[i]的因数还可以用一些技巧在O(A)的预处理下O(lgA)的做,这样进一步降为O(nlgA)。但是比较复杂不讲。

对于计算部分,非常显然的,还是考虑cnt数组对答案的贡献。

cnt[d]对答案的贡献即,对于数字个数小于等于cnt[d]的部分,答案至少可以是d。

那这个东西显然可以用一个叫做线段树的东西优化,但其实有更好的做法。

第一种方法,注意到如果选了k个数答案可以做到d,那么选了k-1个数的答案不会比d小。因为你至少可以从d中选择k-1个数。所以如果k的答案是d,那么k-1的答案只需要从d开始往下枚举了。

但是这样复杂度还是不对。解决方法是先把(cnt[d],d)按照第一维降序,第二维降序排序。

记排序后的数组为p,第一维记作p.x,第二维记作p.y。

这样ans[k]=p[cur=1].y。

这是非常显然的,因为最大的cnt一定是n(此时d=1),又因为cnt相同时按照d降序,所以ans[k]只能在cur=1处取得。

推而广之,我们其实是用所有满足p[j].x>=i的p[j].y中选一个最大的作为ans[i]。

然后发现计算答案的区间是一个不断变长的前缀。(就是说j的范围不断变大)

就是说如果假设计算出满足p[j].x>=i的所有j是在区间[1,cur]间,

那么满足p[j].x>=i-1的所有j的区间是[1,cur2],其中cur2>=cur。

所以你可以ans[i-1]=min{ans[i],min{p[j].y}},j>=cur and p[j].x>=i-1。

然后计算出ans[i-1]之后用j更新cur。这样由于j是枚举完1-n的所以这部分复杂度是O(n)的

这样加上排序的复杂度总复杂度就是O(n*sqrt(A)+nlgn)。可以通过本题;

但是实测花了10000 ms非常不好。

其实后面的计算依旧可以优化。

其实也非常简单

我们在预处理的时候,每次cnt[d]++(其中d是a[i]的因数),就ans[cnt[d]]=max(ans[cnt[d]],d)。

这样复杂度是O(n*sqrt(A))。实测只需要240 ms

另一种等价做法复杂度同样优秀,只是复杂度不好分析,就是枚举d,然后对于i<=cnt[d],ans[i]=max(ans[i],d)。

这样枚举d的代价是O(A)的,枚举i的总代价是cnt[1]+cnt[2]+...+cnt[A],可以证明这样做的复杂度依旧是O(n*sqrt(A))的。事实上枚举i的过程就是cnt++的过程,所以复杂度不变,和刚刚的做法是等价的。如果听不懂可以忽略这句话。

如果你会O(lgn)求因数的话,这个题复杂度可以做到O(nlgA),轻松虐此题。

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#define max_size 1000010
#define maxn 10010
using namespace std;
int n,ans[maxn];
struct Number{
int id,val;
}a[max_size],b;
bool cmp(const Number &a,const Number &b)
{
return a.val==b.val?a.id>b.id:a.val>b.val;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<max_size;i++)
a[i].id=i;
for(int i=1;i<=n;i++)
{
int x,sqrtx;
scanf("%d",&x);
sqrtx=sqrt(x+0.5);
for(int j=1;j<=sqrtx;j++)
if(x%j==0)
{
a[j].val++,a[x/j].val++;
if(j*j==x) a[j].val--;
ans[a[j].val]=max(ans[a[j].val],j);
ans[a[x/j].val]=max(ans[a[x/j].val],x/j);
}
}
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: