传送门: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; }