【luogu P3220】与非(构造)(数位DP)(推论)

与非

题目链接:luogu P3220

题目大意

给你一个操作叫做与非,就是把两个数与起来然后取反。
然后给你一些 K 位二进制数,问你可以通过操作得到多少在 L~R 中的数。

思路

很好玩的一道题呢!

你考虑这个操作:\(not(A\& B)\)
自己跟自己:\(not(A\&A)=not(A)\),非操作

操作之后非一下:\(not(not(A\&A))=A\&A\),与操作。
那或操作也有了:\(not(not(A)\&not(B))\)
那异或也有了:(就一个真一个假)\((A\& not(B))or(not(A)\&B)\)

那这四个操作都有了看起来啥都能搞了,那考虑找找例外。
会发现如果对于给出的所有数,存在两位之间要么都是 \(1\) 要么都是 \(0\),你是不可能凑出 \(10,01\) 这些的,所以这个就相当于形成了若干了连通块。

那我们就可以数位 DP 做了。

那我们考虑证一下为什么只有这个是不行的,也就是剩下的情况为啥可以。
(那我们就把这些绑定的看做一个)
我们考虑有异或,我们就用类似线性基的思想,考虑求出只有某一位是 \(1\) 的情况。
这样就可以或得到所有了。

那怎么得到一个位置呢?我们先考虑不管别的地方 ,我们就要一个地方是 \(1\)
那我们可以把所有的数,这一位是 \(1\) 的就正常用,不是的就取反了再用,这样所有得到的与起来,这一位就是 \(1\),而且看起来别的都会是 \(0\)
仔细想想会发现确实是这样的,因为你反证一下就是如果别的一位是 \(1\),那这一位是 \(1\) 的地方它要是 \(1\),是 \(0\) 的它也要是 \(0\),那不就一模一样,可以绑定看做一个了吗?

所以就证了。

代码

#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

const ll N = 1005;
ll n, K, lim[60];
bool sam[60][60], sp[60];
ll L, R, a[N];

ll clac(ll R, ll wei, bool ding) {
	if (wei < 0) return 1;
	if (!ding) {
		ll num = 0; for (ll i = 0; i <= wei; i++) if (sp[i]) num++;
		return (1ll << num);
	}
	
	if (lim[wei] == -1) {
		ll re = 0;
		for (ll i = 0; i <= ((R >> wei) & 1); i++) {
			ll tmp = 0;
			for (ll j = 0; j < wei; j++) if (sam[wei][j]) lim[j] = i;
			re += clac(R, wei - 1, i == ((R >> wei) & 1));
			for (ll j = 0; j < wei; j++) if (sam[wei][j]) lim[j] = -1;
		}
		return re;
	}
	else {
		if (lim[wei] == 1 && !((R >> wei) & 1)) return 0;
		return clac(R, wei - 1, lim[wei] == ((R >> wei) & 1));
	}
}

ll work(ll R) {
	memset(lim, -1, sizeof(lim));
	if (R < 0) return 0;
	return clac(R, K - 1, (R >> K) ? 0 : 1);
}

int main() {
	scanf("%lld %lld %lld %lld", &n, &K, &L, &R);
	for (ll i = 0; i < K; i++) for (ll j = 0; j < K; j++) sam[i][j] = 1;
	for (ll i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		for (ll k = 0; k < K; k++)
			for (ll kk = 0; kk < k; kk++)
				if (((a[i] >> k) & 1) ^ ((a[i] >> kk) & 1)) sam[k][kk] = sam[kk][k] = 0;
	}
	for (ll i = 0; i < K; i++) {
		sp[i] = 1;
		for (ll j = i + 1; j < K; j++) if (sam[i][j]) {sp[i] = 0; break;}
	}
	
	printf("%lld", work(R) - work(L - 1));
	
	return 0;
}
posted @ 2022-07-17 17:07  あおいSakura  阅读(26)  评论(0编辑  收藏  举报