Description
作为一个神秘的电脑高手,Farmer John 用二进制数字标识他的奶牛。
然而,他有点迷信,标识奶牛用的二进制数字,必须只含有K位“1” (1 <= K <= 10)。 当然,每个标识数字的首位必须为“1”。
FJ按递增的顺序,安排标识数字,开始是最小可行的标识数字(由“1”组成的一个K位数)。
不幸的是,他没有记录下标识数字。请帮他计算,第N个标识数字 (1 <= N <= 10^7)。
分析
考场上漏掉了这道水题,没有仔细琢磨一下规律,而是花了大量时间放在第2题上。
方法一,时间O(len*k):
我们的大致的思想就是根据m和n的限制直接构造第n个数
我们可以二分一个长度len,使得在这个长度之内放k位1的方案数>=n的最小的len。
如何算呢?其实就是组合数!dat(len−1)=∑i<=len−1i=1C(i,k−1).
减一是因为不包括这一位的选法。
而二分完后我们就要将这个问题转化成子问题,这是关键,否则这将无法在根本上解决这个问题。
如何转化成子问题呢?
也就是我们如何选下一个1的位置呢?结果就是上面的解法,但关键在如何求出新的n
因为我们选的len一定有dat(len-1)>n,也就是由长度1..len-1组成的数的答案。
然而因为len是最小的满足dat(len-1)>n,所以也就是说dat(len-2)一旦加上了由len-1组成的答案就大于n了。
我们只需要算出n只在由len-1长度组成的这段的答案就行了(不算之前len-2..1的长度的答案)
就是新的n=n-dat(len-2).
这样一步步转化成子问题,在这个途中我们算的都是除去其它长度构成的答案,所以这样是对的。
代码
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1000;
ll n,m;
ll c[N][N],sum[N][N];
ll C(int x,int y){
return c[x+1][y+1];
}
int main(){
scanf("%d%d",&n,&m);
if (n==1) {
for(int i=1;i<=m;i++) printf("1");
return 0;
}
if (m==1){
printf("1");
for(int i=1;i<=n-1;i++) printf("0");
return 0;
}
c[0][0]=1;
for(int i=1;i<=N;i++)
for(int j=1;j<=12;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
for(int i=0;i<=N;i++)
for(int j=0;j<=12;j++)
for(int k=0;k<=i;k++) sum[i][j]+=C(k,j);
int l=1,r=N;int mid;
while (l<r){
mid=(l+r)>>1;
if (sum[mid-1][m-1]>=n) r=mid;else l=mid+1;
}
int len=l-1;
n-=sum[l-2][m-1];
printf("1");
for(int i=m-1;i>0;i--){
l=1,r=len;
while (l<r){
mid=(l+r)>>1;
if (sum[mid-1][i-1]>=n) r=mid;else l=mid+1;
}
for(int j=len;j>l;j--) printf("0");
printf("1");
len=l-1;
n-=sum[l-2][i-1];
}
for(int j=len;j>0;j--) printf("0");
}
方法二,时间O(N*K):
既然有直接构造的,那么就有一步步枚举,模拟的。
我们如何模拟呢?
首先举个例子:1110010
这个例子的下一个就是1110100
再下一个就是1111000
好了我们到此可以先发现一个规律就是将最尾的一个1不断往前移。
那好,下一个便会破坏这个规律,那么我们如何解决这个问题呢?
因为下一个是10000111,也就是会进位。
再举一个例子10011000,下一个是10100001
那么这题便变得很简单了:
我们记录每个1的位置,若是普通情况就直接将最后一个1的位置加1,若是特殊情况就将最后一个调到第一个位置,若是倒数第2个也是特殊情况就调到第2个位置。。直到有一个1往前移一位后不会和它前面1个的1的位置重叠为止,否则,将1一直往后放.
最后知道了1的位置,便可以知道整个数是多少了。
代码(by yuyanke)
var a:array[0..13] of longint;
i,n,k,j:longint;
begin
read(n,k);
for i:=1 to k do a[i]:=i;
for i:=2 to n do
begin
j:=1;
while true do
begin
inc(a[j]);
if a[j]<>a[j+1] then break;
a[j]:=j;
inc(j);
end;
end;
j:=k;
for i:=a[k] downto 1 do
begin
if a[j]=i then
begin
write(1);
dec(j);
end
else write(0);
end;
end.