[HNOI2012]与非

题目

传送门 to DarkBZOJ

思路

显然不会是直接求解,一定要发现一些性质才行。所以我打了个表,试了试 ( 0011 ) 2 (0011)_2 (0011)2 ( 0101 ) 2 (0101)_2 (0101)2 能凑出什么。为啥是这两个数呢?因为这两个数的 01 01 01 组合刚好取遍四种情况。

结果是:能凑出所有数!我非常惊讶。于是我把它加长,每组设为两个,即 ( 00001111 ) 2 (00001111)_2 (00001111)2 ( 00110011 ) 2 (00110011)_2 (00110011)2,结果是:可以凑出所有 ( a a b b c c d d ) 2 (aabbccdd)_2 (aabbccdd)2 形式的数!

然后我在前面的基础上,加入 ( 10011001 ) 2 (10011001)_2 (10011001)2 进行扰动,发现:又可以凑出所有数了!接下来有很多例子,都符合我的猜想:如果两个二进制位,对于所有数字,都是两个相同的数字,那么只能凑出这两个二进制位相同的数;没有别的限制条件

条件的必要性是显然的。只需要证明充分性。首先 nand \text{nand} nand 等价于 and \text{and} and 再取反;而 x nand ⁡ x x\operatorname{nand}x xnandx 就得到 x x x 的按位取反。那么,我们求出的 nand \text{nand} nand 结果立刻跟自己再取一次 nand \text{nand} nand,得到的实际上就是 and \text{and} and

所以我们实际上拥有两种操作: and \text{and} and 和按位取反。

利用归纳法。由于不同二进制位之间独立,将其重排,使得原本我们可以凑出 ( α α α β β β ω ω ω ⋯   ) 2 (\alpha\alpha\alpha\beta\beta\beta\omega\omega\omega\cdots)_2 (αααβββωωω)2 形式的二进制数,即,必须相同的二进制位都是相邻的。此时,新添一个数,不妨设它在 α \alpha α 的部分中有 01 01 01 两种形式。仍然利用重排,使得 0 , 1 0,1 0,1 构成连续段。为了方便书写,每个部分的长度都设定为 2 2 2,不失一般性。

现在我们拥有的数字是: ( a a b b c c ) 2 (aabbcc)_2 (aabbcc)2 ( 01 ? ? ? ? ) 2 (01????)_2 (01????)2 。令 a = 1 a=1 a=1,其余为 0 0 0,得到 ( 110000 ) 2 (110000)_2 (110000)2,与新数字取 nand \text{nand} nand 即得 λ = ( 101111 ) 2 \lambda=(101111)_2 λ=(101111)2 。此时只需要保持 a = 1 a=1 a=1,改变 b , c b,c b,c 等的取值,然后和 λ \lambda λ and \text{and} and,就会留下 ( 10 b b c c ) 2 (10bbcc)_2 (10bbcc)2 。这些数取反又得到 ( 01 b b c c ) 2 (01bbcc)_2 (01bbcc)2 。于是我们看到: a a a 部分必须取相同值的限制消失了!

所以我们现在拥有的数字是 ( a x b b c c ) 2 (axbbcc)_2 (axbbcc)2 ( 01 ? ? ? ? ) 2 (01????)_2 (01????)2 。如果这个新数字中仍然可以 “切开” 一个连续段,那就继续。所以条件的充分性就得证了。

总结一下,步骤很简单,先用全 0 0 0 nand \text{nand} nand 来造出全 1 1 1,再在上面取 and \text{and} and 来随意变换。现在想想,这个结论不是显而易见么?

代码实现上,我就偷了个懒,直接 O ( k 2 n ) \mathcal O(k^2n) O(k2n) 暴力连并查集了。

代码

#include <cstdio> // Who's the principal?
#include <iostream> // He's just amongst us.
#include <algorithm>
#include <cstring> // A man in a case
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
    
    
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
void writeint(unsigned x){
    
    
	if(x > 9) writeint(x/10);
	putchar(char((x%10)^48));
}

const int MAXK = 62, MAXN = 1003;
int fa[MAXK], n, k; llong a[MAXN];
inline int findSet(int x){
    
    
	if(fa[x] == x) return x;
	return fa[x] = findSet(fa[x]);
}

int chose[MAXK];
llong query(llong bound,int tot){
    
    
	if(bound>>k) return 1ll<<tot; // free!
	llong ans = 0;
	for(int i=k-1; ~i; --i){
    
    
		const bool isRt = (findSet(i) == i);
		if(isRt) -- tot; // one more settled
		if(bound>>i&1){
    
     // can freely choose
			// choose 0
			if(isRt || chose[findSet(i)] == 0)
				ans += 1ll<<tot; // freely choose
			// choose 1 (lean on the bound)
			if(!isRt && !chose[findSet(i)])
				break; // cannot go on
			else chose[findSet(i)] = 1;
		}
		else{
    
     // can only choose 0
			if(!isRt && chose[findSet(i)])
				break; // cannot go on
			else chose[findSet(i)] = 0;
		}
	}
	return ans;
}

int main(){
    
    
	n = readint(), k = readint();
	llong l, r; scanf("%lld %lld",&l,&r);
	rep(i,1,n) scanf("%lld",&a[i]);
	rep(i,0,k-1) fa[i] = i; // init
	rep(i,0,k-1) drep(j,k-1,i+1){
    
    
		if(findSet(i) == findSet(j)) continue;
		bool same = true;
		rep(p,1,n) if((a[p]>>i&1) != (a[p]>>j&1)){
    
    
			same = false; break; // not linked
		}
		if(same){
    
     fa[i] = j; break; }
	}
	int tot = 0; // how many component
	rep(i,0,k-1) tot += (findSet(i) == i);
	printf("%lld\n",query(r+1,tot)-query(l,tot));
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/qq_42101694/article/details/122323162
Recomendado
Clasificación