1. 程式人生 > >BZOJ 2728 HNOI2012 與非 高斯消元

BZOJ 2728 HNOI2012 與非 高斯消元

sca 邏輯 都是 -- cstring 位運算 不同的 ret asi

題目大意:給定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 與非 高斯消元