[BZOJ2728][HNOI2012]与非(并查集+数位DP)

NOT A=A NAND A NOT  A = A  NAND  A

A AND B=NOT(A NAND B) A  AND  B = NOT ( A  NAND  B )

A OR B=(NOT A)NAND(NOT B) A  OR  B = ( NOT  A ) NAND ( NOT  B )

A XOR B=((NOT A)AND B)OR(A AND(NOT B)) A  XOR  B = ( ( NOT  A ) AND  B ) OR ( A  AND ( NOT B ) )

从上面 4 4 个式子得出一个神奇的性质—— NAND 可以表示出所有的位运算!
但这样是否就可以表示出所有 [0,2K) [ 0 , 2 K ) 内的数呢?答案是否定的。
考虑到如果存在 i,j[1,K] i , j ∈ [ 1 , K ] ,使得 每个 Ax A x 1xn 1 ≤ x ≤ n ) 都满足 Ax A x 的第 i i 位和第 j 位相等,那么不管怎样位运算,位运算结果的第 i i 位和第 j 位总是相等的。所以用一个并查集维护位之间的相等关系,如果找到上面这样条件的 i i j ,就把 i i j 所在集合并起来。
这样,在区间 [0,2K) [ 0 , 2 K ) 内能计算出的数的个数就是 2 2 集 合 个 数
但我们要计算的是 [L,R] [ L , R ] 而不仅仅是 [0,2K) [ 0 , 2 K ) ,所以还要进行 数位 DP
先预处理出 prei p r e i :是否存在一个 j>i j > i 使得 j j i 在同一个集合内,有则为 j j ,否则为 0
先把询问拆成 [0,R] [ 0 , R ] [0,L1] [ 0 , L − 1 ] ,设现在要求 [0,m] [ 0 , m ] d(x) d ( x ) m m x 位的值。
定义状态 f[i][0/1] f [ i ] [ 0 / 1 ] 表示从第 K K 位到第 i 位,小于 / 等于 m m 的第 K 位到第 i i 位的个数。边界 f[K][...] 直接处理,然后从 K K 1 转移:
f[i][0]={f[i+1][0]f[i+1][0]×2prei0prei=0 f [ i ] [ 0 ] = { f [ i + 1 ] [ 0 ] p r e i ≠ 0 f [ i + 1 ] [ 0 ] × 2 p r e i = 0

f[i][1]={0f[i+1][1]prei0 AND d(i)d(prei)ELSE f [ i ] [ 1 ] = { 0 p r e i ≠ 0  AND  d ( i ) ≠ d ( p r e i ) f [ i + 1 ] [ 1 ] ELSE

此外,如果 d(i)=1 d ( i ) = 1 ,还有一个转移:
f[i][0]+={0f[i+1][1]prei0 AND d(i)=d(prei)ELSE f [ i ] [ 0 ] + = { 0 p r e i ≠ 0  AND  d ( i ) = d ( p r e i ) f [ i + 1 ] [ 1 ] ELSE

最后答案 f[1][0]+f[1][1] f [ 1 ] [ 0 ] + f [ 1 ] [ 1 ]
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
typedef long long ll;
const int N = 1005, M = 65;
int n, K, fa[M]; ll L, R, a[N], f[M][3], orz[M];
int cx(int x) {
    if (fa[x] != x) fa[x] = cx(fa[x]); return fa[x];
}
void zm(int x, int y) {
    int ix = cx(x), iy = cx(y); if (ix != iy) fa[iy] = ix;
}
int cmp(int x, int y) {
    if (x < y) return 0; if (x > y) return 2; return 1;
}
ll solve(ll m) {
    if (m == -1) return 0; int i; memset(f, 0, sizeof(f));
    f[K][cmp(0, m >> K - 1)]++; f[K][cmp(1, m >> K - 1)]++;
    Rof(i, K - 1, 1) {
        f[i][0] = f[i + 1][0] * (orz[i] ? 1 : 2);
        int x = (m >> i - 1) & 1, y = orz[i] ? (m >> orz[i] - 1) & 1 : -1;
        f[i][1] = f[i + 1][1] * (orz[i] ? x == y : 1);
        if (x) f[i][0] += f[i + 1][1] * (orz[i] ? x != y : 1);
    }
    return f[1][0] + f[1][1];
}
int main() {
    int i, j, k; cin >> n >> K >> L >> R;
    L = max(L, 0ll); R = min(R, (1ll << K) - 1);
    For (i, 1, K) fa[i] = i; For (i, 1, n) scanf("%lld", &a[i]);
    For (i, 1, K) For (j, i + 1, K) {
        bool flag = 1; For (k, 1, n)
            if (((a[k] >> i - 1) & 1) != ((a[k] >> j - 1) & 1))
                {flag = 0; break;}
        if (flag) zm(i, j);
    }
    For (i, 1, K) For (j, i + 1, K) if (cx(i) == cx(j)) orz[i] = j;
    cout << solve(R) - solve(L - 1) << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值