奶牛编号(推荐)

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(len1)=i<=len1i=1C(i,k1).
减一是因为不包括这一位的选法。
而二分完后我们就要将这个问题转化成子问题,这是关键,否则这将无法在根本上解决这个问题。
如何转化成子问题呢?
也就是我们如何选下一个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.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值