洛谷1414 又是毕业季II

个人觉得这个题是一个不错的数论入门题。
首先显然你不能枚举选了哪些数,这样复杂度爆表。
迫不得已考虑枚举另一些东西,例如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[n]中有多少个数字是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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值