• LightOJ1298 One Theorem, One Year(DP + 欧拉函数性质)


    题目

    Source

    http://www.lightoj.com/volume_showproblem.php?problem=1298

    Description

    A number is Almost-K-Prime if it has exactly K prime numbers (not necessarily distinct) in its prime factorization. For example, 12 = 2 * 2 * 3 is an Almost-3-Prime and 32 = 2 * 2 * 2 * 2 * 2 is an Almost-5-Prime number. A number X is called Almost-K-First-P-Prime if it satisfies the following criterions:

    1. X is an Almost-K-Prime and
    2. X has all and only the first P (P ≤ K) primes in its prime factorization.

    For example, if K=3 and P=2, the numbers 18 = 2 * 3 * 3 and 12 = 2 * 2 * 3 satisfy the above criterions. And 630 = 2 * 3 * 3 * 5 * 7 is an example of Almost-5-First-4-Pime.

    For a given K and P, your task is to calculate the summation of Φ(X) for all integers X such that X is an Almost-K-First-P-Prime.

    Input

    Input starts with an integer T (≤ 10000), denoting the number of test cases.

    Each case starts with a line containing two integers K (1 ≤ K ≤ 500) and P (1 ≤ P ≤ K).

    Output

    For each case, print the case number and the result modulo 1000000007.

    Sample Input

    3
    3 2
    5 4
    99 45

    Sample Output

    Case 1: 10
    Case 2: 816
    Case 3: 49939643

    分析

    题目大概说,定义,一个数为Almost-K-First-P-Prime当且仅当这个数由K个质因子组成,且这K个质因子包含且仅包含于前P个质数。给定k和p,求Σphi(AkFpP)。

    首先要知道欧拉函数这几个性质:

    1. φ(p)=p-1(p是质数)
    2. φ(p*a)=(p-1)*φ(a)(p是质数且p不能整除a)
    3. φ(p*a)=p*φ(a)(p是质数且p|a)

    然后,可以考虑用DP来解,利用上面的性质去转移。

    一开始我这么想的,首先由于k个质因子中那p个是一定要有的,先把它们固定下来,即∏prime[1...p],其phi值为∏(prime[1...p]-1)。然后还剩下k-p个质因子要确定,有p个质因子可以选择,这其实就是完全背包问题了:p种物品体积都为1选了之后价值*prime[i],背包容量k-p,问所有选择方案的价值和。

    不过这样会TLE的,数据有10000组,通过可以枚举p把所有情况预处理出来,O(P2K)的时间复杂度,可能会超时,不过已经爆内存了。

    事实上还有更直接的预处理方式:

    • dp[i][j]表示Σphi(Almost-i-First-j-Prime)

    考虑这么转移:

    • 如果prime[j]只出现一次,那么就是从dp[i-1][j-1](这个状态prime[j]不会出现,出现的是前j-1个质数)通过第i个质因子选择prime[j]转移:dp[i][j]+=dp[i-1][j-1]*(prime[j]-1)
    • 如果prime[j]出现多于一次,那么就是从dp[i-1][j](这个状态prime[j]至少出现一次,再加上一次就大于1次了)转移了:dp[i][j]+=dp[i-1][j]*prime[j]

    时间复杂度就是O(PK)

    其实这种转移的分析方式觉得挺强的,分成等于1、大于等于1,这两个能分别求出来且互补的子问题。和排队购票那个经典题的转移一样。

    下面是那两个算法的代码实现。

    代码

    O(P2K)

    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    int prime[555];
    
    bool is_prime(int n){
        for(long long i=2; i*i<=n; ++i){
            if(n%i==0) return 0;
        }
        return 1;
    }
    
    long long d[501][501][500];
    
    void init(){
        int n=0;
        for(int i=2; n!=500; ++i){
            if(is_prime(i)){
                prime[++n]=i;
            }
        }
        for(int p=1; p<=500; ++p){
            d[p][0][0]=1;
            for(int i=1; i<=p; ++i){
                d[p][0][0]*=prime[i]-1;
                d[p][0][0]%=1000000007;
            }
            for(int i=1; i<=p; ++i){
                for(int j=0; j<500; ++j){
                    if(j) d[p][i][j]=d[p][i-1][j]+d[p][i][j-1]*prime[i];
                    else d[p][i][j]=d[p][i-1][j];
                    d[p][i][j]%=1000000007;
                }
            }
        }
    }
    
    int main(){
        init();
        int t,k,p;
        scanf("%d",&t);
        for(int cse=1; cse<=t; ++cse){
            scanf("%d%d",&k,&p);
            printf("Case %d: %lld
    ",cse,d[p][p][k-p]);
        }
        return 0;
    }
    

    O(PK)

    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    int prime[555];
    
    bool is_prime(int n){
        for(long long i=2; i*i<=n; ++i){
            if(n%i==0) return 0;
        }
        return 1;
    }
    
    long long d[555][555];
    
    void init(){
        int n=0;
        for(int i=2; n!=500; ++i){
            if(is_prime(i)){
                prime[++n]=i;
            }
        }
        d[0][0]=1;
        for(int i=1; i<=500; ++i){
            for(int j=1; j<=i; ++j){
                d[i][j]+=d[i-1][j-1]*(prime[j]-1);
                if(i-1>=j) d[i][j]+=d[i-1][j]*prime[j];
                d[i][j]%=1000000007;
            }
        }
    }
    
    int main(){
        init();
        int t,k,p;
        scanf("%d",&t);
        for(int cse=1; cse<=t; ++cse){
            scanf("%d%d",&k,&p);
            printf("Case %d: %lld
    ",cse,d[k][p]);
        }
        return 0;
    }
    
  • 相关阅读:
    spring 注解验证@NotNull等使用方法
    CyclicBarrier 是什么?怎么用?
    多线程
    线程8锁
    java8 CompletableFuture 用法全解
    阻塞队列,有界队列,无界队列
    ArrayBlockingQueue讲解及源码解析
    多线程中使用静态方法存在线程安全的问题
    什么是函数式接口?
    js高级闭包的理解及应用
  • 原文地址:https://www.cnblogs.com/WABoss/p/5767332.html
Copyright © 2020-2023  润新知