BZOJ 2728: [HNOI2012]与非(位运算)

题意

定义 NAND(与非)运算,其运算结果为真当且仅当两个输入的布尔值不全为真,也就是 A NAND B = NOT(A AND B) ,运算位数不会超过 k 位,

给你 n 个整数 Ai ,这些数能任意进行无数次与非运算,最后问能运算出多少个在 [L,R] 区间的数。

k60,n1000,Ai<2k,0LR1018

题解

参考了 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 位,如果所有 Ak 的第 i 位和第 j 位相同,那么最后的结果对于 i,j 这两位一定是一样的。

否则这两位的取值互不影响,可以任意取都能构造出一组合法方案。

我们就能把 k 位数划分成许多个等价类,每个等价类里面的元素取值都必须一样。

然后为了算 [l,r] 区间的答案,我们令 Calc(r)r 能凑出来的数。

那么答案为 Calc(r)Calc(l1) 。至于如何算 Calc(x) 呢?我们按位考虑就行了。

  1. 具体的,如果枚举的位为 0 ,那么忽略。

  2. 如果为 1 ,假设这一位不能选,那么接下来以任意选而不会超也不会重复,所以方案数加上 2sum ( sum 为接下来的集合的个数) 然后退出。

    如果能选。要么不选,那么 2sum1 ;要么将集合中的数全部选了,再接着枚举后面。、

复杂度是 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; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/10096371.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(282)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示