1. 程式人生 > >[USACO12FEB]牛的IDCow IDs 一題多解(求二進位制中有k個1 ,第n大的數)

[USACO12FEB]牛的IDCow IDs 一題多解(求二進位制中有k個1 ,第n大的數)

題目:

FJ給他的奶牛用二進位制進行編號,每個編號恰好包含K 個"1" (1 <= K <= 10),且必須是1開頭。FJ按升序編號,第一個編號是由K個"1"組成。

請問第N(1 <= N <= 10^7)個編號是什麼。

 

不同尋常的暴力:

樣例是升序的第7個,我把1--7都列出來。

1 1 1

1 0 1 1

1 1 0 1

1 1 1 0

1 0 0 1 1

1 0 1 0 1

1 0 1 1 0

發現的規律是,每次將二進位制串的從右往左數的第1個前面為0的1往左移一位。

這個1右邊的1全部靠後。  其實想想也很容易可以想出答案:既然我要移動1的位置了,那肯定是高位拉 , 那我後面的1就是反正最後咯,這樣才可以是理論上的最小;

a[i]表示的是第i個1的位置,初始值為(按陽曆來說)第一個1的位置是1,第二個

1的位置是2,第三個1的位置是3.二進位制也就是1 1 1 。

能移動的條件是,當前1的位置+1不等於

下一個1的位置,也就是前面是空的。然後就這樣啊....

#include<cstdio>
using namespace std;
int n,k,j;
int a[13];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=k;i++)a[i]=i;
    for(int i=2;i<=n;i++){
        j
=1; while(1){ a[j]++; if(a[j]!=a[j+1])break; a[j]=j; j++; } } j=k; for(int i=a[k];i>=1;i--){ if(a[j]==i)printf("1"),j--; else printf("0"); } return 0; }
View Code

 

組合數學法:(摘取一片部落格 , 我還不是很qq理解)

一個只有0和1的數字串,只有1對數字串大小有影響,0沒有影響,大小取決於1的位置和數量。

因為題目中要求出第n個編號是什麼,並且這道題有一個限制:第一位必須是0,那麼我們先將這個串用足夠大小儲存,足夠大的話我們可以新增前導0,到最後從第一個非0位輸出即可,也就是說我們要找到一個m,使得C(m,k) >= n,可以二分求m。

當k=1直接特判掉。

從大到小確定每一位。

如果做到第i位,之前已經填了j個1,那麼這一位填0的方案數就是C(i-1,k-j),即還剩i-1位可以填k-j個1的方案數。

如果這個數小於n,那麼這一位填1,並且n要減去這個數,否則這一位填0。

不過這個組合數會非常大,還會爆long long,需要分類討論進行二分求m.一定要注意這點,第一次提交就在這裡wa的QwQ

時間複雜度O(sqrt(n)k)

#include<cstdio>
#include<iostream>
using namespace std;
const int maxn = 10000010;
long long n , k , m;
long long num[maxn] , cnt;
long long mid , l , r;
long long zuhe(int x , int y)
{
    long long k = 1;
    for(int i = x;i > x - y;i --)
    {
        k *= i;
    }
    for(int i = y;i > 1;i --)
    {
        k /= i;
    }
    return k;
}
int main(){
    scanf("%lld%lld" , &n , &k);
    if(k == 1)
    {
        for(int i = n;i > 0;i --)
        {
            if(i == n)
            {
                printf("1");
            }
            else printf("0");
        }
        puts("");
        return 0;
    }
    else {
//分類討論二分求m
        if(k == 10)
        {
            l = 1;
            r = 600;
            while(l <= r)
            {
                mid = (l + r) / 2;
                if(zuhe(mid , k) >= n)
                {
                    m = mid;
                    r = mid - 1;
                }
                else {
                    l = mid + 1;
                }
            }
        }
        else {
            if(k >= 7)
            {
                l = 1;
                r = 1000;
                while(l <= r)
                {
                    mid = (l + r) / 2;
                    if(zuhe(mid , k) >= n)
                    {
                        m = mid;
                        r = mid - 1;
                    }
                    else {
                        l = mid + 1;
                    }
                }
            }
            else {
                l = 1;
                r = 7000;
                while(l <= r)
                {
                    mid = (l + r) / 2;
                    if(zuhe(mid , k) >= n)
                    {
                        m = mid;
                        r = mid - 1;
                    }
                    else {
                        l = mid + 1;
                    }
                }
            }
        }
        for(int i = m;i > 0;i --)
        {
            long long t = zuhe(i - 1 , k);
            if(t < n)
            {
                num[i] = 1;
                n -= t;
                k --;
                if(!cnt)
                {
                    cnt = i;
                }
            }
            if(!k || !n)
            {
                break;
            }
        }
        for(long long i = cnt;i > 0;i --)
        {
            printf("%d" , num[i]);
        }
    }
   puts("");
    return 0;
}
View Code

 

動態規劃:

太菜了沒有想到組合數

我們也醫用數位dp去做;

f[i][j] 表示在前i位我們放j個1的情況有幾種

f[i][j]=f[i-1][j]+f[i-1][j];

然後我們直接大力的從字串的高位開始列舉

如果這個位子不放我們後來所有的方法都不夠了那就放

#include<bits/stdc++.h>
#define Ll long long
using namespace std;
const Ll N=12,M=1e5+5;
Ll a[M],f[M][N];
Ll n,m,ok,v;
int main()
{
    scanf("%lld%lld",&m,&n);
    if(n==1){
        cout<<1;
        for(int i=1;i<m;i++)printf("0");
        return 0;
    }
    for(Ll i=0;i<=1e5;i++)f[i][0]=1;
    for(Ll i=1;i<=1e5;i++)if(!v)
        for(Ll j=1;j<=n;j++){
            f[i][j]=f[i-1][j]+f[i-1][j-1];
            if(j==n&&f[i][j]>=m){v=i;break;}
        }
    for(Ll i=v;i;i--){
        if(f[i-1][n]<m)
            a[i]=1,m-=f[i-1][n],n--;
        if(a[i])ok=1;
        if(ok)cout<<a[i];
    }
}
View Code

 

組合數字最快然後超神暴力然後動態規劃