• HDU5812 Distance(枚举 + 分解因子)


    题目

    Source

    http://acm.hdu.edu.cn/showproblem.php?pid=5812

    Description

    In number theory, a prime is a positive integer greater than 1 that has no positive divisors other than 1 and itself. The distance between two positive integers x and y, denoted by d(x, y), is defined as the minimum number of multiplications by a prime or divisions (without a remainder) by a prime one can perform to transform x into y. For example, d(15, 50) = 3, because 50 = 15 * 2 * 5 / 3, and you have to perform two multiplications (*2, *5) and one division (/3) to transform 15 into 50.

    For a set S of positive integers, which is initially empty, you are asked to implement the following types of operations on S.

    1. I x: Insert x into S. If x is already in S, just ignore this operation.
    2. D x: Delete x from S. If x is not in S, just ignore this operation.
    3. Q x: Find out a minimum z such that there exists a y in S and d(x, y) = z.

    Input

    The input contains multiple test cases. The first line of each case contains an integer Q (1 <= Q <= 50000), indicating the number of operations. The following lines each contain a letter ‘I’, ‘D’ or ‘Q’, and an integer x (1 <= x <= 1000000).
    Q = 0 indicates the end of the input.
    The total number of operations does not exceed 300000.

    Output

    For each case, output “Case #X:” first, where X is the case number, starting from 1. Then for each ‘Q’ operation, output the result in a line; if S is empty when a ‘Q’ operation is to perform, output -1 instead.

    Sample Input

    12
    I 20
    I 15
    Q 30
    I 30
    Q 30
    D 10
    Q 27
    I 15
    D 15
    D 20
    D 30
    Q 5
    0

    Sample Output

    Case #1:
    1
    0
    3
    -1

    分析

    题目大概说,定义d(x,y)为x通过乘或除以质数变为y的最少运算次数。现在有一个集合,有插入一个数到集合的操作,也有从集合中删除一个数的操作,还有查询操作:输出最小的d(a,b),a是所查询的数,b是集合中的任一数。

    题解这么说的:

    不难发现d(a, b) = f(a/gcd(a, b)) + f(b/gcd(a,b)),其中f(x)表示x的质因子个数. 因而当遇到操作Q x时,我们只需要枚举x的每个约数y,看属于当前集合的y的所有倍数z中f(z/y)的最小值为多少. 为了快速求出这个最小值,我们用C[y][s]表示当前集合中y的所有倍数z中使得f(z/y)=s的z的数量. 因为s的值不会超过20,所以可以用位压缩的方法,用D[y]表示y的倍数中哪些s值出现了,这样查询最小的s值可以通过位运算快速求出(因为时限是标程的3倍,所以也不会特意卡掉其它方法). 插入和删除x时同样可以通过枚举x约数的方法来更新C[y][s]和D[y]的值. 设M表示元素的最大值,因为1到M所有约数的数量是O(MlogM)的,所以算法的时间和空间复杂度也都是O(MlogM)的. 又因为操作数少于M,所以实际情况还会更好一些.

    首先是d(a, b) = f(a/gcd(a, b)) + f(b/gcd(a,b)),这个是显然的,而f(x)可以通过线性筛求得。

    然后,对于每一个查询,枚举约数cd(注意,这个约数的个数在1000000内最多为128个,即2*3*5*7*11*13*17=510510的约数个数)。

    • f(a/cd)这个能求得;而对于f(b/cd),这个就需要在更新集合过程中做一些处理——
      • 插入数x到集合时,同样也是枚举数x的约数d,然后把f(x/d)的值更新到各个约数d的信息中。对于从集合中删除数的操作同样反过来做。
    • 于是对于各个cd,我们就能获得集合更新过程中最小的f(b/cd)。

    更新集合维护各个约数最小的那个值,可以用官方题解说的那样,也能用网上其他题解用的multiset。

    我都有尝试,代码见下。不过官方题解的做法我跑了1000多秒,写得太挫了吧。另外感觉,对一些东西都不敏感,约数个数,质因子个数等等其实都是很小的,没这种概念。

    代码

    multiset

    #include<cstdio>
    #include<cstring>
    #include<set>
    #include<algorithm>
    using namespace std;
    
    int prime_cnt[1000001],prime[1000001],pn;
    bool vis[1000001];
    
    multiset<int> mset[1000001];
    
    void insert(int n){
        if(vis[n]) return;
        vis[n]=1;
        for(long long i=1; i*i<=n; ++i){
            if(n%i==0){
                int tmp=n/i;
                mset[i].insert(prime_cnt[tmp]);
                if(tmp!=i) mset[tmp].insert(prime_cnt[i]);
            }
        }
    }
    void remove(int n){
        if(!vis[n]) return;
        vis[n]=0;
        for(long long i=1; i*i<=n; ++i){
            if(n%i==0){
                int tmp=n/i;
                mset[i].erase(mset[i].find(prime_cnt[tmp]));
                if(tmp!=i) mset[tmp].erase(mset[tmp].find(prime_cnt[i]));
            }
        }
    }
    int query(int n){
        int res=11111111;
        for(long long i=1; i*i<=n; ++i){
            if(n%i==0){
                int tmp=n/i;
                if(!mset[i].empty()){
                    res=min(res,prime_cnt[tmp]+*mset[i].begin());
                }
                if(tmp!=i && !mset[tmp].empty()) res=min(res,prime_cnt[i]+*mset[tmp].begin());
            }
        }
        if(res==11111111) return -1;
        return res;
    }
    
    int main(){
         for(long long i=2; i<1000001; ++i){
            if(!vis[i]) prime[pn++]=i,prime_cnt[i]=1;
            for(int j=0; j<pn && i*prime[j]<1000001; ++j){
                vis[i*prime[j]]=true;
                prime_cnt[i*prime[j]]=prime_cnt[i]+1;
                if(i%prime[j]==0) break;
            }
        }
        int q,cse=0;
        while(~scanf("%d",&q) && q){
            printf("Case #%d:
    ",++cse);
            memset(vis,0,sizeof(vis));
            for(int i=0; i<1000001; ++i) mset[i].clear();
            while(q--){
                char op; int a;
                scanf(" %c",&op); scanf("%d",&a);
                if(op=='I'){
                    insert(a);
                }else if(op=='D'){
                    remove(a);
                }else{
                    printf("%d
    ",query(a));
                }
            }
        }
        return 0;
    }
    

    官方题解

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    int prime_cnt[1000001],prime[1000001],pn;
    bool vis[1000001];
    
    int C[1000001][20],D[1000001];
    
    void insert(int n){
        if(vis[n]) return;
        vis[n]=1;
        for(long long i=1; i*i<=n; ++i){
            if(n%i) continue;
            int j=n/i;
    
            ++C[i][prime_cnt[j]];
            D[i]|=(1<<prime_cnt[j]);
    
            if(i!=j){
                ++C[j][prime_cnt[i]];
                D[j]|=(1<<prime_cnt[i]);
            }
        }
    }
    void remove(int n){
        if(!vis[n]) return;
        vis[n]=0;
        for(long long i=1; i*i<=n; ++i){
            if(n%i) continue;
            int j=n/i;
    
            if(--C[i][prime_cnt[j]]==0) D[i]^=(1<<prime_cnt[j]);
    
            if(i!=j && --C[j][prime_cnt[i]]==0) D[j]^=(1<<prime_cnt[i]);
        }
    }
    int posi[1000001];
    int query(int n){
        int res=1111111;
        for(long long i=1; i*i<=n; ++i){
            if(n%i) continue;
            int j=n/i;
            if(D[i]){
                res=min(res,prime_cnt[j]+posi[D[i]&-D[i]]);
            }
            if(D[j]){
                res=min(res,prime_cnt[i]+posi[D[j]&-D[j]]);
            }
        }
        if(res==1111111) return -1;
        return res;
    }
    
    int main(){
        for(long long i=2; i<1000001; ++i){
            if(!vis[i]) prime[pn++]=i,prime_cnt[i]=1;
            for(int j=0; j<pn && i*prime[j]<1000001; ++j){
                vis[i*prime[j]]=true;
                prime_cnt[i*prime[j]]=prime_cnt[i]+1;
                if(i%prime[j]==0) break;
            }
        }
    
        for(int i=0; i<20; ++i){
            posi[1<<i]=i;
        }
    
        char op; int a;
        int q,cse=0;
        while(~scanf("%d",&q) && q){
            printf("Case #%d:
    ",++cse);
            memset(vis,0,sizeof(vis));
            memset(C,0,sizeof(C));
            memset(D,0,sizeof(D));
            while(q--){
                scanf(" %c",&op); scanf("%d",&a);
                if(op=='I'){
                    insert(a);
                }else if(op=='D'){
                    remove(a);
                }else{
                    printf("%d
    ",query(a));
                }
            }
        }
        return 0;
    }
    
  • 相关阅读:
    do you believe it?
    Linux+Mono+Asp.net入门:00软件下载地址
    《行动英雄》李嘉诚讲演
    Js全局和局部变量_this关键字_《没有控件的ASPDONET》
    教学相长
    基于多线程和SOCKET的聊天室(原创附源码)【没有控件的ASP.NET】
    程序员学好英语啊
    SQL行列转换:报表_公司采购表_每个公司各采购了些什么产品
    关于文件后缀名的误解
    Linux+Mono+Asp.net入门+Asp.net:01.虚拟机安装
  • 原文地址:https://www.cnblogs.com/WABoss/p/5759927.html
Copyright © 2020-2023  润新知