传送门:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2728

 

神题!!!!!!

此题初看乃数位dp,但是半天想不到状态,卡在如何判断当前数能否被nand出来。

但实际上,举几个例子可以发现(具体看神犇博客),nand操作可以表示任意位运算,可以说对每个位置,0和1都有可能被nand出来

于是我们考虑两个位置之间的关系,发现,若要nand出来的数第i位和第j位一定相同,当且仅当a[1]~a[n]的第i位和第j位都相同。

所以我们只需用O(n * k ^ 2)的预处理(并查集),得到一些一定相等的位置的集合,然后看可以组成多少个在[l, r]的数就行了。

设dp(i)表示小于i的能被组合出的数的个数,则答案为dp(r) - dp(l - 1)。

接下来我们考虑怎么求这个数,枚举当前数的最高位的1用不用上。

若不用上,则答案加上2^p,其中p为最高位小于当前数最高位的集合的个数

若用上,答案加1,当前数减去这个集合,重复进行第1步

 

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int maxn = 1010;
LL a[maxn];
int fa[maxn];
LL val[maxn];
int n, k;
LL l, r;
int find(int x) {
    if(!fa[x]) return x;
    else return find(fa[x]);
}
LL mx(LL x) {
    for(int i = k; i ; i --) {
        if((x >> (i - 1)) & 1) return i;
    }
    return 0;
}
LL dp(LL x) {
    if(x <= 0) return -1;
    if(x > (1LL << k) - 1) x = (1LL << k) - 1;
    LL res = 0;
    while(x > 0) {
        int t = mx(x);
        int num = 0;
        for(int j = 1; j <= k; j ++) {
            if(j != find(j)) continue;
            if(j >= t) continue;
            num ++;
        }
        res += (1LL << num) - 1;
        x -= val[find(t)];
        if(x >= 0) res ++;
    }
    return res;
}
int main() {
    scanf("%d%d", &n, &k);
    scanf("%lld%lld", &l, &r);
    for(int i = 1; i <= n; i ++) {
        scanf("%lld", &a[i]);
    }
    for(int i = 1; i <= k; i ++) {
        for(int j = i + 1; j <= k; j ++) {
            bool flag = false;
            for(int p = 1; p <= n; p ++) {
                if(((a[p] >> (i - 1)) & 1) != ((a[p] >> (j - 1)) & 1)) {
                    flag = true;
                    break;
                }
            }
            if(!flag) {
                int t1 = find(i), t2 = find(j);
                if(t1 < t2) fa[t1] = t2;
                else if(t1 > t2) fa[t2] = t1;
            }
        }
    }
    for(int i = 1; i <= k; i ++) {
        val[find(i)] |= (1LL << (i - 1));
    }
    printf("%lld\n", dp(r) - dp(l - 1));
    return 0;
}