题目链接:
http://acm.timus.ru/problem.aspx?space=1&num=1091
题目大意:
给你两个整数K和S,从小于等于S的非负整数中选择K个数,并且K个数的最大公约数大于1,
问总共有多少组。(2 <= K <= S <= 50)。
解题思路:
因为 2 <= K <= S <= 50,我们可以直接枚举质因数,求出从每个质因数的倍数中选择k个数
的组合数,累加起来即为方案个数,但是这样重复计算了很多情况。
例如:S = 20,K = 2。
2的倍数:2、4、6、8、10、12、14、16、18、20
3的倍数:3、6、9、12、15、18
5的倍数:5、10、15、20
7的倍数:7、14
11的倍数:11
……
如果单纯累加的话,很多数都重复计算了多次。应该去掉重复多加的部分。相当于把2的倍数、
3的倍数、…全部看作一个个的集合,求每部分的组合情况,然后求集合的并集。
如果2的倍数集合中选取K的数的方案数为P(2),所求结果为:
P(2)+P(3)+…+P(23)-P(2*3)-P(2*5)....(2*11) + P(2*3*5) + ……
因为S最大数不超过50,K最小数为2,则2*29 = 54 > 50了,所以质因数枚举到23即可。
求集合的并集利用容斥原理来做。
AC代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
int Prime[22] = {2,3,5,7,11,13,17,19,23,29};
int C(int n,int m)
{
if(n < m) //不加就错
return 0;
m = min(m, n-m);
int ans = 1;
for(int i = 1; i <= m; ++i)
{
ans *= (n-i+1);
ans /= i;
}
return ans;
}
int K,S;
int Solve()
{
int ans = 0,top = 0;
for(int i = 0; i < 10; ++i)
{
if(S / Prime[i] < K)
{
top = i;
break;
}
}
for(int i = 1; i < (1 << top); ++i)
{
int odd = 0,Mult = 1;
for(int j = 0; j < top; ++j)
{
if((1 << j) & i)
{
odd++;
Mult *= Prime[j];
}
}
if(odd & 1)
ans += C(S / Mult, K);
else
ans -= C(S / Mult, K);
}
if(ans <= 10000)
return ans;
else
return 10000;
}
int main()
{
while(~scanf("%d%d",&K,&S))
{
printf("%d\n",Solve());
}
return 0;
}