C - One Theorem, One Year (快速幂+大力素数筛+dp)

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 Xis 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

Note

1.      In mathematics Φ(X) means the number of relatively prime numbers with respect to Xwhich are smaller than X. Two numbers are relatively prime if their GCD (Greatest Common Divisor) is 1. For example, Φ(12) = 4, because the numbers that are relatively prime to 12 are: 1, 5, 7, 11.

2.      For the first case, K = 3 and P = 2 we have only two such numbers which are Almost-3-First-2-Prime, 18=2*3*3 and 12=2*2*3. The result is therefore, Φ(12) + Φ(18) = 10.


//Full of love and hope for life

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <vector>
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//https://paste.ubuntu.com/
//https://www.cnblogs.com/zzrturist/    //博客园
//https://blog.csdn.net/qq_44134712     //csdn

using namespace std;
const int maxn=5e3+10;
typedef long long ll;
const int mod=1e9+7;

vector<int>vis;
int prime[maxn];
ll dp[510][510];
ll ma[510][510];

void pri(){//素数筛
    memset(prime,0,sizeof(prime));
    prime[1]=1;
    for(int i=2;i<=maxn;i++){
        if(prime[i]==0){
            vis.push_back(i);
            for(int j=i+i;j<=maxn;j+=i){
                prime[j]=1;
            }
        }
    }
}

ll qpow(ll a,ll b){//快速幂
    ll ans=1;
    while(b){
        if(b&1){
            ans=(ans*a)%mod;
        }
        a=(a*a)%mod;
        b/=2;
    }
    return ans;
}

void init(){
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=1;i<=500;i++){
        for(int j=1;j<=500;j++){
            ma[i][j]=qpow(vis[i-1],j-1);
        }
    }
    for(int i=1;i<=500;i++){
        for(int j=i-1;j<=500;j++){
            for(int k=1;k<=500-j;k++){
                dp[i][j+k]+=(dp[i-1][j]*ma[i][k]%mod)*(vis[i-1]-1);//还是不大懂
                dp[i][j+k]%=mod;
            }
        }
    }
}

int main()
{
    int T,k=0;
    int n,m;
    pri();
    init();
    cin >> T;
    while(T--){
        k++;
        cin >> n >> m;
        printf("Case %d: %lld\n",k,dp[m][n]);
    }
    return 0;
}

分析

题目大概说,定义,一个数为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\n",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\n",cse,d[k][p]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZ --瑞 hopeACMer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值