被神仙题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\n"); else if(L==0)printf("%lld\n",solve(R)); else printf("%lld\n",solve(R)-solve(L-1)); return 0; }