• bzoj2728: [HNOI2012]与非


    被神仙题cao爆

    可以发现(并没有)nand可以把这些基本运算表示出来

    !a=a nand a

    a&b=!(a nand b)

    a|b=(!a)nand(!b)

    用类似线性基的做法,由高位到低位枚举,对于每一个位,因为有取反操作,在保证当前位为1的情况下,把全部的数字都and一次,那么就可以得到一个由1个或多个1组成类似基底的东西,枚举时已经出现过1的位就不管。然后最后or起来的方案数就2的个数次方,但是有可能超过限制。

    考虑上限为u,假如当前的这个基底比u小,那么u取0后面可以任选,方案数为2^i-1,然后取1往后延伸,此时u减掉这个基底。否则就只能取0,往后延伸。

    记得补上取0的情况

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long LL;
    
    int n,m;LL a[1100];
    int plen;LL p[110];
    LL solve(LL u)
    {
        LL ans=0;
        for(int i=1;i<=plen;i++)
            if(p[i]<=u)
                u-=p[i], ans|=(1LL<<plen-i);
        return ans+1;
    }
    bool v[110];
    int main()
    {
        LL L,R;
        scanf("%d%d%lld%lld",&n,&m,&L,&R); R=min(R,(1LL<<m)-1);
        for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
        plen=0;
        memset(v,false,sizeof(v));
        for(int j=m-1;j>=0;j--)
        {
            if(v[j]==false)
            {
                plen++; p[plen]=(1LL<<m)-1;
                for(int i=1;i<=n;i++)
                    if(a[i]&(1LL<<j))p[plen]&=a[i];
                    else p[plen]&=~a[i];
                
                for(int i=0;i<m;i++)
                    if(p[plen]&(1LL<<i))v[i]=true;
            }
        }
        if(L>R)printf("0
    ");
        else if(L==0)printf("%lld
    ",solve(R));
        else printf("%lld
    ",solve(R)-solve(L-1));
        return 0;
    }
  • 相关阅读:
    最小花费
    LOJ10090
    LOJ2436
    loj10087
    LOJ2632
    LOJ10021 Addition Chains
    LOJ10019生日蛋糕
    loj10018数的划分
    LOJ10015扩散
    loj10014数列分段二
  • 原文地址:https://www.cnblogs.com/AKCqhzdy/p/9672581.html
Copyright © 2020-2023  润新知