P2527 [SHOI2001]Panda的烦恼-题解

ZhanPJ 发布于 2021-12-07 32 次阅读


题目传送门:P2527

题意简化:

已知有 n 个质数 a_i,求第 k 个包含了这些质因子的数。

算法:

这个蒟蒻看了各位大佬的 O(nk) 算法,表示不懂,于是现在这个使用了优先队列,也就是 O(k \times n \times \log(n \times k)),所以开个氧气也能过。

思路:

笔者第一次做,发现题目中明确指出了 ans \leq 2 \times 10^9,所以想起来了要开 long long,刚好学习了优先队列。而优先队列有一个特性——可以排序,然后……写出了这个代码。

ll number[20005];
int bfs(int a[]){
    int cnt=n,id=0,nbs=n;
    for(int i=1;i<=n;i++)q.push(a[i]);
    while(!q.empty()){
        id++;
        int top=q.top();
        q.pop();
        if(id==k)return top;
        if(cnt>k)continue;
        for(int i=1;i<=n;i++){
            bool flag=1;int t=a[i]*top;
            for(int i=1;i<=nbs;i++){
                if(number[i]==t){flag=0;break;}
            }
            if(flag)q.push(t);
        }
    }
}

很恭喜,成功地只 AC 了一个点,还 TLE 了一个点。

现在我们可以发现,判重实际上很浪费时间,所以现在我们记录上一次的数字(因为一个大的数字不可能凑出来一个小的数字),节约时间。
再一次观察发现,函数内的 int 和优先队列使用的 long long 不为同类型,所以需要将 int 修改为 long long
于是有了现在这个代码。

int bfs(int a[]){
    int id=0;
    ll last=-1,top,t;
    for(int i=1;i<=n;i++)q.push(a[i]);
    while(!q.empty()){
        top=q.top();
        q.pop();
        if(top==last)continue;
        id++;
        if(id==k)return top;
        last=top;
        for(int i=1;i<=n;i++){
            t=a[i]*top;
            if(t>P)continue;
            q.push(t);
        }
    }
}

很恭喜,还是 TLE 了一个点。
到了这里,笔者想起来开 O2 优化,开了优化后,是 MLE,爆空间了。
所以……你还记得我们说的那个 ans \le 2 \times 10^9 吗?
既然 ans \leq 2 \times 10^9int 的最大值近似为 2^{31}-1 约为 2.147 \times 10^9),那么答案就在 int 类型的区间里。
所以,我们把改成 int 类型的队列。
很恭喜,开 O2 优化,过了。

停一下,我们整理一下思路:
1. 结果需要开 long long,因为两个大于等于 2^{15.5} 的数字相乘,是会超过 int 的最大值的,但是队列只用开 int,因为答案不超过 int 的最大范围。
2. 不需要记录结果数字有没有重复(因为两个大于一的数字相乘一定越乘越大),但是需要记录这个数字有没有用过。
3. 需要 O2 优化。可能是 STL 的常数比较大。
ACcode:

//头文件略
#define ll long long
int n,k;
priority_queue< int , vector<int> , greater<int> > q;
const int P=2000000000;//方便之后书写
ll bfs(int a[]){
    int id=0;
    sort(a+1,a+n+1);//这里可以方便后面的if(t>P)可以直接break,方便我们节约时间复杂度。
    for(int i=1;i<=n;i++)q.push(a[i]);//入队
    ll last=-1,top,t;//last代表上一次的数字
    while(!q.empty()){
        top=q.top();
        q.pop();
        if(top==last)continue;//跳过循环
        id++;last=top;//id统计有多少个有效数字了
        if(id==k)return top;//当前是正确的数字,那么返回队列的顶端
        for(int i=1;i<=n;i++){
            t=a[i]*top;
            if(t>P)break;//特判
            q.push(t);//入队
        }
    }
}
int main(){
    //读入略
    printf("%d",bfs(input));

    return 0;
}

附:提交记录