BZOJ 2728: [HNOI2012]与非(位运算)
题意
定义 NAND
(与非)运算,其运算结果为真当且仅当两个输入的布尔值不全为真,也就是 A NAND B = NOT(A AND B)
,运算位数不会超过 \(k\) 位,
给你 \(n\) 个整数 \(A_i\) ,这些数能任意进行无数次与非运算,最后问能运算出多少个在 \([L, R]\) 区间的数。
\(k \le 60, n \le 1000, A_i < 2^k, 0 \le L \le R \le 10^{18}\)
题解
参考了 kczno1 孔爷的题解。
这个运算初看不太优美,其实我们可以利用它的一些性质。
由于 A NAND A = NOT (A AND A) = NOT A
所以我们就可以得到了 NOT
(非) 运算。
进一步我们利用 NOT
,NOT(A NAND B) = NOT(NOT(A AND B)) = A AND B
,就可以得到了 AND
(与)运算。
这样这个运算就变得十分优秀了,我们就转化成进行 NOT
和 AND
然后我们就可以得到 所有二进制操作 了。
然后利用这个性质,我们就可以得到一个更加有用的性质。
对于第 \(i\) 位和第 \(j\) 位,如果所有 \(A_k\) 的第 \(i\) 位和第 \(j\) 位相同,那么最后的结果对于 \(i, j\) 这两位一定是一样的。
否则这两位的取值互不影响,可以任意取都能构造出一组合法方案。
我们就能把 \(k\) 位数划分成许多个等价类,每个等价类里面的元素取值都必须一样。
然后为了算 \([l, r]\) 区间的答案,我们令 \(Calc(r)\) 为 \(\le r\) 能凑出来的数。
那么答案为 \(Calc(r) - Calc(l - 1)\) 。至于如何算 \(Calc(x)\) 呢?我们按位考虑就行了。
-
具体的,如果枚举的位为 \(0\) ,那么忽略。
-
如果为 \(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;
}