乘方 [二分答案, 容斥]

乘 方 乘方



正 解 部 分 \color{red}{正解部分}

二分出一个 x x x, 检查 小于等于 x x x 的有多少数字,

可以想到将每个 S ( n i ) S(n_i) S(ni)小于等于 x x x 的数字个数计算出来, 然后减去 重复 的,

小于等于 x x x 的数字个数可以通过 二分 计算出来,

对于 重复 的, 对每个 n a , n b n_a, n_b na,nb 减去 S ( L c m ( n a , n b ) ) S(Lcm(n_a, n_b)) S(Lcm(na,nb)), 加上 S ( L c m ( n a , n b , n c ) ) S(Lcm(n_a, n_b, n_c)) S(Lcm(na,nb,nc)) 再减去 …

于是对于每个 S ( n i ) S(n_i) S(ni), 都对应着一个 容斥系数 f [ n i ] f[n_i] f[ni],

现在去计算 f [ n i ] f[n_i] f[ni] 是多少, 可以使用 dp, 设 F [ i , j ] F[i, j] F[i,j] 表示前 i i i 个数组成的集合中 j j j容斥系数 是多少,

F [ i , L c m ( j , A i ) ] = F [ i − 1 , L c m ( j , A i ) ] − ∑ F [ i − 1 , j ] F[i, Lcm(j, A_i)] = F[i-1, Lcm(j, A_i)] - \sum F[i-1, j] F[i,Lcm(j,Ai)]=F[i1,Lcm(j,Ai)]F[i1,j] .

于是就可以 O ( q log ⁡ 3 1 0 17 ) O(q \log^3 10^{17}) O(qlog31017) 计算答案了 .


实 现 部 分 \color{red}{实现部分}

#include<bits/stdc++.h>
#define reg register
typedef long long ll;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

const int maxn = 205;

int M;
int K;
int A[maxn];

ll F[maxn][70];

int Gcd(int a, int b){ return !b?a:Gcd(b, a%b); }

int Lcm(int a, int b){ return a/Gcd(a, b)*b; }

bool Ksmcompar(ll a, int b, ll x){
        ll s = 1;
        while(b){
                if(b & 1){
                        if(s > x/a) return 0;
                        s *= a;
                }
                b >>= 1;
                if(a > x/a && b) return 0;
                a *= a;
        }
        return 1;
}

ll chk(ll x){
        ll res = 0;
        for(reg int i = 1; i <= 60; i ++){
                if(!F[K][i]) continue ;
                ll l = 0, r = 1e17, s = 0;
                while(l <= r){
                        //printf("%lld %lld %lld\n", x, l, r);
                        ll mid = l+r >> 1ll;
                        if(Ksmcompar(mid, i, x)) l = mid + 1, s = mid;
                        else r = mid - 1;
                }
//                printf("FUck %lld\n", s-1);
                res += 1ll*F[K][i]* (s-1);
        }
        return res+1; // !
}

void Work(){
        M = read(), K = read();
        for(reg int i = 1; i <= K; i ++) A[i] = read();
        memset(F, 0, sizeof F);
        for(reg int i = 1; i <= K; i ++){
                for(reg int j = 1; j <= 60; j ++) F[i][j] = F[i-1][j];
                F[i][A[i]] ++;
                for(reg int j = 1; j <= 60; j ++){
                        int to = Lcm(A[i], j);
                        if(!F[i-1][j] || to > 60) continue ;
                        F[i][to] -= F[i-1][j];
                }
        }
        ll l = 1, r = 1e17, Ans = 1;
        while(l <= r){
                ll mid = l+r >> 1;
                if(chk(mid) >= M) Ans = mid, r = mid - 1;
                else l = mid + 1;
        }
        printf("%lld\n", Ans);
}

int main(){
        int T = read(); while(T --) Work();
        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值