1. 程式人生 > 實用技巧 >LuoguP6218 [USACO06NOV] Round Numbers S

LuoguP6218 [USACO06NOV] Round Numbers S

題目描述

如果一個正整數的二進位制表示中,\(0\)的數目不小於\(1\)的數目,那麼它就被稱為「圓數」。

例如,\(9\)的二進位制表示為\(1001\),其中有\(2\)\(0\)\(2\)\(1\)。因此,\(9\)是一個「圓數」。

請你計算,區間\([l,r]\)中有多少個「圓數」。

輸入格式

一行,兩個整數\(l\)\(r\)

輸出格式

一行,一個整數,表示區間\([l,r]\)中「圓數」的個數。

樣例

輸入:\(2 12\)         輸出: \(6\)

思路

顯然這道題又是一道數位DP。但是這個題的難點和特殊之處就在於它是在二進位制下處理的。這就需要我們重新揣度此題的狀態。

\(f[i][j][k]\)表示一個有\(i\)位,且其中包括\(j\)\(1\),且從右往左數第\(i\)個數是\(k\)的圓數的個數。這個如何轉移呢?

顯然的是,若\(j < i\),  \(f[i][j][0]=f[i-1][j][0]+f[i-1][j][1]\),若\(j !=0\), 則\(f[i][j][1]=f[i-1][j-1][0]+f[i-1][j-1][1]\) (這個感性理解一下?)

最後就分成兩種情況處理:

1.將位數小於cnt位的圓數累加到答案中

2.和其他數位DP類似,考慮用添數的方法解決問題。若當前遍歷到該位為\(1\),那麼就存在該位為\(0\)的情況,這時候再列舉\(1\)

的位數,考慮還是否能構成圓數,若能構成,累加答案即可。

最後字首和統計答案即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
typedef long long ll;
int l, r;
ll f[35][35][2];
inline int read(void){
    int f = 1, x = 0;char ch;
    do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
    do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
    return f * x;
}
inline void _init(void){
    f[1][0][0] = 1, f[1][1][1] = 1;//初始化
    for (int i = 2; i <= 32;++i){
        for (int j = 0; j <= i;++j){
            if(j<i) f[i][j][0] = f[i - 1][j][0] + f[i - 1][j][1];//若既有0又有1或只有0
            if(j) f[i][j][1] = f[i - 1][j - 1][0] + f[i - 1][j - 1][1];//若有1或全部是1
        }
    }
    return;
}
inline ll calc(int x){
    int bn[35], cnt = 0;
    ll res = 0;
    if(x==0) return 1;
    while(x){
        bn[++cnt] = x & 1;
        x >>= 1;
    }//二進位制拆分
    for (int i = 1; i < cnt;++i){
        for (int j = 0; j <= (i >> 1); ++j)
            res += f[i][j][1];
    }//統計小於cnt位的圓數,(i>>1)這個就是保證了所得的數一定為圓數
    int s0 = 0, s1 = 1;//s1一定要賦值為1,因為無論如何我們討論的數都是大於1的(為0的情況在開頭就舍掉了)
    for (int i = cnt - 1; i >= 1;--i){
        if(bn[i]) for (int j = 0; j <= i;++j){
            if(s0+i-j>=s1+j) res += f[i][j][0];//已確定的0的個數+列舉的0的個數>=已確定的1的個數+列舉的1的個數
        }//                                          s0        +  i  -  j   >=     s1       +     j
        bn[i] ? ++s1 : ++s0;
    }
    return res;
}
int main(){
    l = read(), r = read();
    _init();
    printf("%lld\n", calc(r + 1) - calc(l));
    return 0;
}