1. 程式人生 > >BZOJ 2728: [HNOI2012]與非(位運算)

BZOJ 2728: [HNOI2012]與非(位運算)

題意

定義 NAND(與非)運算,其運算結果為真當且僅當兩個輸入的布林值不全為真,也就是 A NAND B = NOT(A AND B) ,運算位數不會超過 \(k\) 位,

給你 \(n\) 個整數 \(A_i\) ,這些數能任意進行無數次與非運算,最後問能運算出多少個在 \([L, R]\) 區間的數。

題解

參考了 kczno1 孔爺的題解。

這個運算初看不太優美,其實我們可以利用它的一些性質。

由於 A NAND A = NOT (A AND A) = NOT A 所以我們就可以得到了 NOT (非) 運算。

進一步我們利用 NOTNOT(A NAND B) = NOT(NOT(A AND B)) = A AND B

,就可以得到了 AND (與)運算。

這樣這個運算就變得十分優秀了,我們就轉化成進行 NOTAND 然後我們就可以得到 所有二進位制操作 了。

然後利用這個性質,我們就可以得到一個更加有用的性質。

對於第 \(i\) 位和第 \(j\) 位,如果所有 \(A_k\) 的第 \(i\) 位和第 \(j\) 位相同,那麼最後的結果對於 \(i, j\) 這兩位一定是一樣的。

否則這兩位的取值互不影響,可以任意取都能構造出一組合法方案。

我們就能把 \(k\) 位數劃分成許多個等價類,每個等價類裡面的元素取值都必須一樣。

然後為了算 \([l, r]\) 區間的答案,我們令 \(Calc(r)\)

\(\le r\) 能湊出來的數。

那麼答案為 \(Calc(r) - Calc(l - 1)\) 。至於如何算 \(Calc(x)\) 呢?我們按位考慮就行了。

  1. 具體的,如果列舉的位為 \(0\) ,那麼忽略。

  2. 如果為 \(1\) ,假設這一位不能選,那麼接下來以任意選而不會超也不會重複,所以方案數加上 \(2^{sum}\) ( \(sum\) 為接下來的集合的個數) 然後退出。

    如果能選。要麼不選,那麼 \(2^{sum-1}\) ;要麼將集合中的數全部選了,再接著列舉後面。、

複雜度是 \(O(nk)\) 的,不知道為什麼 \(n\) 只開 \(1000\) 。。。石樂志。

程式碼

#include <bits/stdc++.h>

#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl

using namespace std;

typedef long long ll;

template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }

template<typename T>
inline T read() {
    T x(0), sgn(1); char ch(getchar());
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
    for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
    return x * sgn;
}

void File() {
#ifdef zjp_shadow
    freopen ("2728.in", "r", stdin);
    freopen ("2728.out", "w", stdout);
#endif
}

typedef long long ll;

const int N = 1e3 + 1e2, K = 60;

ll a[N], sta[N], now, full;

int n, k, sum[K];

inline ll Calc(ll x) {
    if (x >= full) return 1ll << sum[k - 1];
    ll res = 0;
    for (int i = k - 1; x >= 0 && i >= 0; -- i) 
        if (x >> i & 1) {
            if (sta[i]) {
                res += 1ll << (sum[i] - 1); x -= sta[i];
            }
            else {
                res += 1ll << sum[i]; break;
            }
        }
    return res + (x == 0);
}

int main () {

    File();

    n = read<int>(); 
    full = (1ll << (k = read<int>())) - 1;

    ll l = read<ll>(), r = read<ll>();
    For (i, 1, n) a[i] = read<ll>();

    ll have = 0;
    Fordown (i, k - 1, 0) if (!(have >> i & 1)) {
        ll now = full;
        For (j, 1, n)
            now &= (a[j] >> i & 1) ? a[j] : ~a[j];
        have |= (sta[i] = now); sum[i] = 1;
    }
    For (i, 1, k - 1) sum[i] += sum[i - 1];

    printf ("%lld\n", Calc(r) - Calc(l - 1));

    return 0;

}