bzoj 2728: [HNOI2012]与非

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2728
思路:这是不错的题目,做了好久,原因是想错一个地方。
我们首先发现这个长得很像NERD的NAND其实是类似于基本位运算的一种运算法则,我们不禁想要用熟悉的基本位运算表示它,其实观察得到(题目名字是提示):
a NAND b = !a or !b = !( a & b)
so:
a NAND a = !a
a & b = !(a NAND b)
a or b = !a NAND !b
把三种基本运算都联系起来了,实际上NAND功能极为强大,能做到三种运算,而且实际上是一一对应的,于是我们将问题转化为用三种基本的位运算能表示多少 [L,R] 的数字。
然而我们发现运算过程中使用 ! 其实可以转化为初始的A数组取!然后加入运算,这时运算只剩下or和&的了,又考虑a or b,将其分为三部分,a & b,a - a & b,b - a & b,(-的定义类似于集合差)其中a - a & b = a & !b,同理b - a & b,所以
a or b = (a & b) or (a - a & b) or (b - a & b) ,我们发现,我们的or运算也几乎没有用啦!我们可以通过&运算分解为一些较小的基底(类似于线性基),最后一步再将基底 or 起来就好,于是我们第一步将 ! 去掉,最后一步把or去掉,仅剩&分解小基底了,考虑每一位若包含这一位的所有A & 起来只剩下这一位,则得到这一位独立基底,否则得到一个多个位同时存在的基底,很明显,这些基底没有交集(否则可以分解为更小的基底)将他们随意组合的答案都不相同,所以 ans=2 ,然而随意组合的数可能超过 L 或者R,所以考虑从高到低枚举超出的和 L R lcp ,然后减去超出的就好啦。
复杂度: ONK+K2)

代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#define N 1000
#define C 60
using namespace std;
typedef long long LL;
LL n,k,l,r,a[(N << 1) + 5],f[C + 5],g[C + 5],ans;
bool vis[C + 5];
inline LL getnum(){
    char c;LL num,flag = 1;
    while (!isdigit(c = getchar())) 
      if (c == '-') flag = -1;
    num = c - '0';
    while (isdigit(c = getchar())) num = 10 * num + c - '0';
    return num * flag;
}

inline void init(){
    memset(a,0,sizeof(a));
    n = getnum(); k = getnum(); l = getnum(); r = getnum();
    for (LL i = 1;i <= n; ++i) a[i] = getnum();
    for (LL i = 1;i <= n; ++i)
      for (LL j = 0;j < k; ++j)
        if (!(a[i] & (1LL << j))) a[i + n] += (1LL << j);
}

inline void DO_IT(){
    LL sum;
    memset(f,-1,sizeof(f));
    memset(g,0,sizeof(g));
    for (LL i = k - 1;i >= 0; --i)
      if (f[i] < 0)
    {
        sum = (1LL << k) - 1;
        for (LL j = 1;j <= (n << 1); ++j)
          if (a[j] & (1LL << i)) sum &= a[j];
        for (LL j = k - 1;j >= 0; --j)
          if (sum & (1LL << j)) f[j] = i;
        g[i + 1] = 1;   
    }
    for (LL i = 1;i <= k; ++i) g[i] += g[i - 1];
}

inline LL query(LL x){
    if (x == -1) return 0;
    LL num = 0,sum,p = 0;
    ans = (1 << g[k]);
    for (LL i = C - 1;i >= 0; --i){
      if (i == 9) 
      ++p;
      if (!((1LL << i) & x))
    {
        bool flag = 1;
        memset(vis,0,sizeof(vis));
        for (LL j = C - 1;j > i; --j)
          if (x & (1LL << j)) 
            if (f[j] < 0) { flag = 0; break; }
            else vis[f[j]] = 1;
        if (f[i] < 0) flag = 0;
        else vis[f[i]] = 1;
        if (flag){
        sum = 0; 
        for (LL j = C - 1;j >= i; --j)
          if (f[j] >= 0)
            if (vis[f[j]]) ++sum;
        if (sum == num + 1) ans -= (1LL << g[i]); }
    }
    else ++num;
   }
    return ans;
}

int main(){
    init();
    DO_IT();
    cout<<query(r) - query(l - 1);
    return 0;
}

注意:1.当遇到大数还涉及循环变量的运算要开LL
2.运算例如(1 << 33)是错误的,一定要记得在数字后加LL
如:(1LL << 33LL)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值