链接:点击打开链接
题意:找到一个数n,使得n + 1, n + 2, ..., 2·n这n个数的集合中有且仅有m个:其二进制表示中包含k个1
题解:1. 首先二分答案肯定是可行的,因为数n -> n+1的时候,集合的数减少了一个n+1,多了一个2n+1和2n+2,而2n+2的二进制中包含1的个数和n+1是一样的,所以所求的答案对n是单调增的。二分n然后判断可行性就行了。
2. 第二种解法是用组合数,考虑f(n,k)是所有小于n的数中包含k个1的数的个数
假设n的二进制中有r个1,分别在a1,a2,...,ar 位置。
1. 如果a1(最高位的1)换成0,那么剩下的位随意放k个1即可满足所求,共C(a1, k)种
2. 如果a1仍位1,a2所在的位置换成0,a2往后的位随意放k-1个满足所求, 共C(a2, k-1)种
......
于是f(n,k) = C(a1, k) + C(a2, k-1) + C(a3, k-2) + .... + C(a k+1, 0);
很容易发现n变成2n的话就是组合数的每个底加了1
f(2n,k) = C(a1+1, k) + C(a2+1, k-1) + C(a3+1, k-2) + .... + C(a k+1 +1, 0);
题目即要使得:f(2n,k) - f(n,k) == m
这个推一下公式就发现是sigma(C(ai-1, k-i+1)) == m.
于是就可以构造了,每次取一个小于m的最大的C(i, k),把第i位放成1,然后k-1,直到k == 0为止
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<set>
#include<vector>
#include<algorithm>
using namespace std;
typedef unsigned long long LL;
typedef pair<LL,int> P;
const int N = 60;
LL n;
int k;
LL C[N][N];
set<P> vec[N];
set<P>::iterator ite;
bool vis[N];
void solve()
{
int kk = k-1;
LL ans = 0;
if(n == 0 && k != 1){
ans = 1;
}
while(n){
ite = vec[kk].upper_bound(make_pair(n,100));
--ite;
ite = vec[kk].lower_bound(make_pair((*ite).first, 0));
vis[(*ite).second] = 1;
n -= (*ite).first;
vec[kk].erase(ite);
kk--;
}
for(int i = 0 ;i < N ; i++){
if(vis[i]){
ans += ((LL)1 << i);
}
}
cout << ans << endl;
}
int main()
{
memset(C,0,sizeof(C));
C[0][0] = 1;
vec[0].insert(make_pair(C[0][0],0));
for(int i = 1 ; i < N ; i ++){
C[i][0] = 1;
vec[0].insert(make_pair(C[i][0],i));
for(int j = 1 ;j <= i ; j ++){
C[i][j] = C[i][j-1] * (i-j+1) / j;
vec[j].insert(make_pair(C[i][j],i));
}
}
cin >> n >> k;
solve();
return 0;
}