BZOJ - 2728 与非
题意:
给定N个数,一个数k和一个范围[L,R]。每个数可以使用任意次,k表示与非不超过k位。求出范围内有多少个数可以由他们的与非和表示。
题解:
m个数进行NAND,最终的数二进制下某一位如果为1,那么这m个数二进制下这一位也肯定为1。所以如果N个数某些位相同的话就可以把这些位并成一个基。
因为aNANDa = !a,所以某一位如果为0可以转化为1。
然后用预处理的基从高位开始贪心的选择。如果当前位取0,则情况数为(1<<cnt-i)-1(不包含0);当前位取1时情况数为1。一共是(1<<cnt-i)。
题目中L的范围给错了,可能为0。所以当L为0时应该+1即-(-1)。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1005; int n, k, cnt; ll l, r, noww; int vis[N]; ll a[N], w[70]; ll digit(ll x) { if(x==-1) return -1; ll res = 0, val = 0; for(int i = 1; i <= cnt; i++) { if(val+w[i] <= x) { val += w[i]; res += (1ll<<cnt-i); } } return res; } int main() { scanf("%d%d%lld%lld", &n, &k, &l, &r); for(int i = 1; i <= n; i++) scanf("%lld", &a[i]); noww = (1ll<<k)-1; for(int i = k-1; i >= 0; i--) { if(vis[i]) continue; ll ed = noww; for(int j = 1; j <= n; j++) if(a[j]&(1ll<<i)) ed &= a[j]; else ed &= (~a[j]); for(int j = 0; j < i; j++) if(ed&(1ll<<j)) vis[j] = 1; w[++cnt] = ed; } printf("%lld", digit(r) - digit(l-1)); }