2020 China Collegiate Programming Contest, Weihai Site L. Clock Master(分组背包+预处理)

传送门

在这里插入图片描述

题意

给你一个正整数 n n n,你需要将 n n n拆成若干个正整数的和。设拆成了 t 1 , t 2 , . . . , t s {t_1,t_2,...,t_s} t1,t2,...,ts s s s个正整数,对于任意自然数 k k k,存在向量 ( k (k (k m o d mod mod t 1 , {t_1}, t1, k k k m o d mod mod t 2 , . . . , {t_2},..., t2,..., k k k m o d mod mod t s ) {t_s}) ts),这些向量中的不同向量个数记为 x x x
因此每种拆分对应一个不同的向量个数,现在让你求一个使 x x x最大的拆分,输出 ln ⁡ x \ln x lnx

数据范围: 1 ⩽ n ⩽ 3 × 1 0 4 1\leqslant n\leqslant 3\times10^4 1n3×104

思路

一个结论: x = l c m ( t 1 , t 2 , . . . , t s ) x=lcm(t_1,t_2,...,t_s) x=lcm(t1,t2,...,ts)
由于本文主要侧重分组背包部分,对这个结论感兴趣的读者可以自己到网上寻找证明。
② 接下来就是考虑怎么样拆分。考虑最优解一定存在拆成的数两两互质的形式,多余的部分都可以拆成若干个 1 1 1来凑数( 1 1 1与任何数互质),这样计算答案就变成了计算 ln ⁡ t 1 + ln ⁡ t 2 + . . . + ln ⁡ t s \ln t_1+\ln t_2+...+\ln t_s lnt1+lnt2+...+lnts
证明: 假设最优解为 t 1 , t 2 , . . . , t s {t_1,t_2,...,t_s} t1,t2,...,ts
1、若这些数两两互质,则成立。
2、若 g c d ( t x , t y ) = d > = 2 gcd(t_x,t_y)=d>=2 gcd(tx,ty)=d>=2,则将 t x t_x tx从解中移除,将 t x / d t_x/d tx/d t x − t x / d t_x-t_x/d txtx/d 1 1 1加入到解中,原解的值不变。
3、重复步骤2直到所有数两两互质。
③考虑拆成若干个质数的幂次形式,即每个 t t t都是 p i {p^i} pi的形式,其中 p p p为质数, i i i为自然数。
一个 p i {p^i} pi的价值是 ln ⁡ p i \ln {p^i} lnpi,这不就是背包吗。
④对于每个 p p p,只能选一次 p i {p^i} pi,但不同的质数是可以任意选的,因此做一个分组背包,先筛一下质数,然后按 p p p分组。预处理答案然后 O ( 1 ) O(1) O(1)回答。
⑤总时间复杂度为 O ( n O(n O(n ∑ i = 2 , i 为 质 数 n log ⁡ i n \sum_{i=2,i为质数}^n \log_i n i=2,inlogin ) ) ) ln ⁡ t \ln t lnt需要预处理不然会T。

#include<bits/stdc++.h>
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
const int maxn=4e4+5;
const double ln = log(exp(1));
template <typename T>
inline void read(T &X){
    X=0;int w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    if(w) X=-X;
}
int n,m,k,cnt;
int a[maxn],c[maxn];
double dp[maxn];
double ans[maxn],lg[maxn];
int flag;
void init(){
    rep(i,2,30000){
        if(!c[i]){
            c[++cnt] = i;
            for (int j = i * 2; j <= 30000;j+=i)
                c[j] = 1;
        }
        lg[i] = log(i) / ln;
    }
}
void solve(){
    n = 30000;
    rep(i,1,cnt){
        dep(j,n,c[i]){
            ll tp = c[i];
            while(tp<=j){
                dp[j] = max(dp[j], dp[j - tp] + lg[tp]);
                tp *= c[i];
            }
        }
    }
    rep(i, 2, n) ans[i] = max(ans[i - 1], dp[i]);
}
int main(){
    init();
    solve();
    int T=1,cas=1;
    read(T);
    while (T--){
        read(n);
        cout << fixed << setprecision(12) << ans[n] << endl;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值