bzoj 4762: 最小集合
明天就是ZJOI Day1啦
恩,实际上很早就写了这一题
然后回来看的时候发现我怎么不会做了……
看着代码尝试理解一下我当时在想什么(捂脸
感觉智商下降的好快QAQ
6.19魔改:
好了,我现在终于知道当时我在写些什么了……
一个AND和为\(0\)的集合\(S\)合法,当且仅当它所有大小为\(|S - 1|\)的集合AND和都不为\(0\)。
也就是说有\(|S|\)个限制,每个限制为某个集合的AND和不为\(0\)
如果令\(S_{a}\)为\(S\)删去\(a\)这个元素所形成的集合,令\(f(S)\)为\(S\)集合中所有元素的AND和
那么答案就是$$\sum_{S}[(f(S) = 0) \wedge \bigwedge _{a\in S} (f(S_{a})\neq0)]$$
这玩意是很难直接dp的,因此容斥这些限制
也就是说要去求补集:求某些限制同时不合法的方案数
于是有这么一个式子:
$$[\bigwedge _{a\in S} (f(S_{a})\neq0)]= \sum_{S'\subseteq S}[\bigwedge _{a\in S'} (f(S_{a})=0)](-1)^{|S'|}$$
注意到$$[\bigwedge _{a\in S'} (f(S_{a})=0)] = [\text{OR}_{a\in S'}f(S_a) = 0]$$
也就是如果某些限制同时不满足的话,那么如果把所有对应集合的AND和 按位或起来的和为0
因此答案就等于$$\sum_{S}\sum_{S'\subseteq S}[(f(S) = 0) \wedge \text{OR}_{a\in S'}f(S_a) = 0](-1)^{|S'|}$$
暴力的话枚举集合\(S\),枚举\(S\)的子集\(S'\)就可以算贡献了
如果要DP的话只需要记录\(S\)的AND和,\(S'\)的\(\text{OR}_{a\in S'}f(S_a)\),对应的贡献的话就可以DP了
令\(f_{i,k,j}\) 为 考虑了前\(i\)个数,前者的值为\(k\),后者的值为\(j\)的答案
若第\(i + 1\)个数为\(d\)
如果不选第\(i + 1\)个数,则有:\(f_{i, k, j} \to f_{i + 1, k, j}\)
如果\(S\)中选第\(i + 1\)个数,但\(S'\)中不选,则有:\(f_{i, k, j} \to f_{i + 1, k \& d, j \& d}\)
如果\(S\)中选第\(i + 1\)个数,且\(S'\)中也选,则有:\(-f_{i, k, j} \to f_{i + 1, k \& d, k | j \& d}\)
初始状态为\(f_{0, 1023, 1023} = 1\)
最终答案\(ans = f_{n, 0, 0}\)
于是就好了
#include <bits/stdc++.h> #define N 1030 #define MOD 1000000007 using namespace std; int n; int f[2][N][N], tmp; void ADD(int &t, int d) { t += d; if (t >= MOD) t -= MOD; } int main() { scanf("%d", &n); f[0][1023][1023] = 1; for (int i = 1; i <= n; ++ i) { int d; scanf("%d", &d); tmp ^= 1; for (int j = 0; j < 1024; ++ j) { for (int k = j; k; k = (k - 1) & j) f[tmp][k][j] = 0; f[tmp][0][j] = 0; } for (int j = 0; j < 1024; ++ j) { for (int k = j; k; k = (k - 1) & j) if (f[!tmp][k][j]) { ADD(f[tmp][k][j], f[!tmp][k][j]); ADD(f[tmp][k & d][j & d], f[!tmp][k][j]); ADD(f[tmp][k & d][k | j & d], MOD - f[!tmp][k][j]); } ADD(f[tmp][0][j], f[!tmp][0][j]); ADD(f[tmp][0][j & d], f[!tmp][0][j]); ADD(f[tmp][0][j & d], MOD - f[!tmp][0][j]); } } printf("%d", f[tmp][0][0]); }