Codecraft-18 and Codeforces Round #458 (Div. 1 + Div. 2, combined) C. Travelling Salesman and Special Numbers (组合数学)
The Travelling Salesman spends a lot of time travelling so he tends to get bored. To pass time, he likes to perform operations on numbers. One such operation is to take a positive integer x and reduce it to the number of bits set to 1 in the binary representation of x. For example for number 13 it's true that 1310 = 11012, so it has 3 bits set and 13 will be reduced to 3 in one operation.
He calls a number special if the minimum number of operations to reduce it to 1 is k.
He wants to find out how many special numbers exist which are not greater than n. Please help the Travelling Salesman, as he is about to reach his destination!
Since the answer can be large, output it modulo 109 + 7.
The first line contains integer n (1 ≤ n < 21000).
The second line contains integer k (0 ≤ k ≤ 1000).
Note that n is given in its binary representation without any leading zeros.
Output a single integer — the number of special numbers not greater than n, modulo 109 + 7.
110
2
3
111111011
2
169
In the first sample, the three special numbers are 3, 5 and 6. They get reduced to 2 in one operation (since there are two set bits in each of 3, 5 and 6) and then to 1 in one more operation (since there is only one set bit in 2).
分析:
2的1000次方是一个很大的数,但是只要经过一次操作之后,就会变成一个小于等于1000的数,所以我们可以先预处理出来所有小于等于1000的数需要多少次操作转化为1
现在的n需要经过x次操作变成1,那么n经过一次操作得到的数就需要经过x-1次操作变成1
根据预处理的结果,我们可以找到哪些数经过x-1次操作变成1
如果找到了数r,那么就需要在小于等于n的范围内,排列r个1
要保证小于等于n的话,可以从最高位开始进行这样的操作,now代表现在的位数,x表示现在需要排列的1的数量
当该位为1的时候,我们考虑:
如果不排该位,则在后面的len-now-1个位置中找x个位置 即C(len-now-1,x)
如果排该位,则x=x-1,now=now+1,继续进行下一位操作
如果该位为0,
则只有直接x=x-1,now=now+1,进行下一位操作。
这样就可以判断x>=2的情况,而当x=0时,只有1满足条件,所以答案为1,当x=1时,我们直接在预处理中找那些能1步操作得到1
代码如下:
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <vector> #include <cmath> using namespace std; typedef long long ll; const int MOD=1e9+7; const int MAXN1=1005; const int MAXN2=1005; ll a[1010]; ll C[MAXN1+5][MAXN2+5]; char str[1010]; ll cal(ll x) { ll num=0; while(x>0) { if(x%2==1) num++; x/=2; } return num; } void init1()//预处理1-1000得到1需要的操作数 { for(ll i=1;i<=1005;i++) { int h=i; while(h!=1) { h=cal(h); a[i]++; } } } void init2() //预处理组合数 { for(int i=0;i<=MAXN1;i++) C[i][0]=1; for(int i=1;i<=MAXN1;i++) { for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD; } } ll so(char str[],ll x,ll now,ll len) { if(now>=len) return 0; ll sum=0; if(str[now]=='1'){ sum=C[len-now-1][x]; if(x==1)sum=(sum+1)%MOD; if(x-1==0)return sum; return (sum+so(str,x-1,now+1,len))%MOD; } else return (sum+so(str,x,now+1,len))%MOD; } int main() { init1(); init2(); ll ans,x,len,flag,rr; scanf("%s",str); scanf("%lld",&x); len=strlen(str); if(x==0) { puts("1"); return 0; } if(len==1&&str[0]=='1') { puts("0"); return 0; } ans=0; for(int i=1;i<=len;i++){ if(a[i]==x-1) { ans+=so(str,i,0,len)%MOD; ans%=MOD; } } if(x==1)printf("%lld\n",ans-1); else printf("%lld\n",ans); return 0; }