JZOJ.1241. Number

Problem

Description

有N(2<=N<=15)个数A1,A2,….,An-1,An,如果在这N个数中,有且仅有一个数能整除m,那么整数m就是一个幸运数,你的任务就是在给定A1,A2,….,An-1,An的情况下,求出第k小的幸运数。

Input

第一行为一整数数N,K(2<=N<=15,1<=K<=2^31-1),意义如上述。
接下来一行有N个整数,A1,A2,….,An-1,An,这N个整数均不超过2^31-1。

Output

输出一行,仅包含一个整数ans,表示第K小的幸运数。答案保证不超过10^15。

Sample Input

输入1:
2 4
2 3
输入2:
2 100
125 32767

Sample Output

输出1:
8
输出2:
12500

Hint

对于50%的数据,N<=5,ANS<=100000
对于80%的数据,N<=10,ANS<=10^15
对于100%的数据,N<=15,ANS<=10^15

Solution

正解:二分+容斥
二分答案mid,现在就是要求在[1,mid]这个范围内,幸运数的个数 Si
则总个数为

Σni=1mida[i]

如果它小于k则mid要增大,否则减小。
但是,这里面一定多算了很多的数。比如说序列{2,3}其中6不是幸运数,2这里算了一次,3这里又算了一次,所以个数要-2.(6是2,3的交集,所以我们要减去2和3的交集)
由容斥原理得,对于一个集合,如果集合元素个数为基数,则加上这个集合所有元素的LCM*集合元素个数否则减去。
由于N<=15,我们可以用 2n 的时间来查找所有的集合。
总时间复杂度为 O(log(1015)2nLCM) .

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define LL long long
using namespace std;
LL a[20],l,mid,r,ans,sum;
int i,n,k;
LL gcd(LL a,LL b)
{
    if (b==0) return a;else return gcd(b,a%b);
}
LL lcm(LL a,LL b)
{
    if (a>b) swap(a,b);
    return a*b/gcd(a,b);
}
void select(LL x,LL s,LL cnt)
{
    if (x>n)
    {
        if (cnt>0)
        {
            if (cnt%2==1) ans+=(mid/s)*cnt;
                     else ans-=(mid/s)*cnt;
        }
        return;
    }
    LL w;
    select(x+1,s,cnt);
    w=lcm(s,a[x]);
    if (w>mid) return;   //特别注意:如果LCM超过了mid要退出去,否则会爆炸。
    select(x+1,w,cnt+1);
}
int main()
{
    scanf("%d%d",&n,&k);
    fo(i,1,n) scanf("%lld",&a[i]);
    l=1;
    r=1000000000000000;
    while (l<r)
    {
        mid=(l+r)/2;
        ans=0;
        select(1,1,0);
        if (ans<k) l=mid+1;else r=mid;
    }
    printf("%lld",l);
}

——2016.7.12

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值