码迷,mamicode.com
首页 > 其他好文 > 详细

BZOJ 2728 HNOI2012 与非 高斯消元

时间:2014-10-14 17:55:39      阅读:185      评论:0      收藏:0      [点我收藏+]

标签:bzoj   bzoj2728   高斯消元   

题目大意:给定k位二进制下的n个数,求[l,r]区间内有多少个数能通过这几个数与非得到

首先观察真值表 我们有A nand A = not A

然后就有not ( A nand B ) = A and B

与和非都弄到了,我们就可以做出一切逻辑运算了,比如说或和异或

A or B = not ( ( not A ) and ( not B ) )

A xor B = ( A or B ) and ( A nand B )

然后我们对于位运算可以发现一个性质

对于某两位来说,如果对于每一个数,这两位上的值都是相同的,那么这两位无论怎么计算最终结果都会是相同的

比如说10(1010)和7(0111),第一位和第三位都是相同的,所以最后无论怎么计算,这两位都是一样的

然后我们这么处理:

对于每一位,我们枚举每一个数,若该数该位上为0,我们就对这个数取非

然后把所有数取与

该位上都是1,所以取与后一定是1;对于其他位,只要有这两位不同的数存在,那么这位一定是0

最后取与的结果中与该位全部相同的位都是1,其余都是0

对于每一位这样处理,标记去重,然后可以得到线性基,保证每一位存在且仅存在于线性基中的一个数上

拿去从大到小贪心处理即可 得到二进制序列即为答案

此题有坑 题目描述中1<=L<=R<=10^18 但是第七个点L=0 坑死一票人啊

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 1010
using namespace std;
typedef long long ll;
int n,k;
ll digit,l,r,a[M],basis[70],tot;
bool v[70];
ll Get_Digit(ll x)
{
	if(x==-1)
		return -1;//坑比!!! 
	ll now=0,re=0;
	int i;
	for(i=1;i<=tot;i++)
		if( (now|basis[i])<=x )
			now|=basis[i],re|=(1ll<<tot-i);
	return re;
}
int main()
{
	
	//freopen("nand.in","r",stdin);
	//reopen("nand.out","w",stdout);
	
	int i,j;
	ll now;
	cin>>n>>k>>l>>r;
	digit=(1ll<<k)-1;
	for(i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	for(i=k-1;~i;i--)
		if(!v[i])
		{
			now=digit;
			for(j=1;j<=n;j++)
				if( a[j]&(1ll<<i) )
					now&=a[j];
				else
					now&=~a[j]&digit;
			basis[++tot]=now;
			for(j=0;j<=i;j++)
				if( now&(1ll<<j) )
					v[j]=1;
		}
	cout<<Get_Digit(r)-Get_Digit(l-1)<<endl;
}
//lld


BZOJ 2728 HNOI2012 与非 高斯消元

标签:bzoj   bzoj2728   高斯消元   

原文地址:http://blog.csdn.net/popoqqq/article/details/40077481

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!