bzoj2728: [HNOI2012]与非
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2728
思路:首先我们要玩出nand的性质
nand可以表示出所有逻辑运算
not a=a nand a
a and b=not (a nand b)
....
这题另一个性质就是如果a[1]~a[n]的所有数第i位和第j位相同,那么nand出来的数第i位和第j位也相同
把取值相同的并到一起,用一个并查集维护一下。
然后我们就要实现一个函数query(x)表示0-x之间有多少个数可以得到
从x的高位向低位做,
如果x该位为1,我们有两种选择
不选这个1,那低位可以任意选,方案+=2^p,不用往下做了 p为还未确定的集合数
选了这个1,低位还不能任意选,继续往下做
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> const int maxn=1010,maxk=70; typedef long long ll; using namespace std; int n,k,bel[maxk],cnt,mark[maxk];ll L,R,a[maxn]; int find(int x){return x==bel[x]?x:bel[x]=find(bel[x]);} bool check(int x,int y){ for (int i=1;i<=n;i++) if (((a[i]>>x)^(a[i]>>y))&1) return 0; return 1; } ll query(ll x){ if (++x>=(1ll<<k)) return 1ll<<cnt; int tmp=cnt;ll res=0;memset(mark,-1,sizeof(mark)); for (int i=k-1;i>=0;i--){ if ((x>>i)&1){ if (mark[find(i)]!=1){ if (mark[find(i)]==-1) tmp--,mark[find(i)]=1; res+=1ll<<tmp; if (!mark[find(i)]) break; } } else{ if (mark[find(i)]==-1) tmp--,mark[find(i)]=0; else if (mark[find(i)]==1) break; } } return res; } int main(){ scanf("%d%d%lld%lld",&n,&k,&L,&R); for (int i=1;i<=n;i++) scanf("%lld",&a[i]); for (int i=0;i<k;i++) bel[i]=i; for (int i=0;i<k;i++) for (int j=0;j<i;j++) if (check(i,j)){bel[find(bel[i])]=find(j);break;} for (int i=0;i<k;i++) if (find(i)==i) cnt++; //printf("%d\n",cnt); //for (int i=0;i<k;i++) printf("%d %d\n",i,bel[i]); printf("%lld\n",query(R)-query(L-1)); return 0; } /* 5 60 0 1234567891012 1234567891277 5674897451677 1895415618481 8741189478971 2106156486449 */