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)∗2n∗算LCM的时间) .
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