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
",cnt);
	//for (int i=0;i<k;i++) printf("%d %d
",i,bel[i]);
	printf("%lld
",query(R)-query(L-1));
	return 0;
}

/*
5 60 0 1234567891012
1234567891277 5674897451677 1895415618481 8741189478971 2106156486449

*/


原文地址:https://www.cnblogs.com/thythy/p/5493480.html