[DP][容斥原理] BZOJ 4762: 最小集合

来源:互联网 发布:json xml yml yaml 编辑:程序博客网 时间:2024/05/14 20:08

Description

定义一个非空集合是合法的,当且仅当它满足以下两个条件。

  • 1、集合内所有元素and和为0
  • 2、它的非空子集中仅有它本身满足1

给出一个集合S,求它的合法非空子集数。
n1000,ai1024

Solution

f(S)=aSa
Sa为集合S删去a所成的集合。
那么就有答案为:
ans=S[(f(Sa)=0)aS(f(Sa)0)]
右边这部分是可以容斥的
[aS(f(Sa)0)]=TS[aT(f(Sa)=0)](1)|T|
还有
[aT(f(Sa)=0)]=[(aTf(Sa))=0]
因为左式相当与把所有f(Sa)按位或起来值是否为0。(类似于补集转化之类的吧)。
所以就有
ans=STS[(f(S)=0)((aTf(Sa))=0)](1)|S|

这样就可以DP了。
需要记录上式前后两个值。
dpi,j,k表示考虑前i个数,jf(S)的值,k为后面那一坨东西的值的方案数。
每次转移考虑是否在ST中加入该数。

  • 若不选入STdpi1,j,kdpi,j,k
  • 若选入S但不选入Tdpi1,j,kdpi,jx,kx
  • 若选入STdpi1,j,kdpi,jx,kxj

可以再所有数之前加一个1023,这样便于初始化,也不会对答案造成影响。
因为是在枚举集合中枚举子集,所以时间复杂度是O(n310)的。

#include <bits/stdc++.h>using namespace std;const int N = 1030;const int MOD = 1000000007;inline char get(void) {    static char buf[100000], *S = buf, *T = buf;    if (S == T) {        T = (S = buf) + fread(buf, 1, 100000, stdin);        if (S == T) return EOF;    }    return *S++;}inline void read(int &x) {    static char c; x = 0;    for (c = get(); c < '0' || c > '9'; c = get());    for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0';}int dp[N][N], dp1[N][N];int n;int a[N];inline void Add(int &x, int a) {    x += a;    while (x >= MOD) x -= MOD;}int main(void) {    read(n);    for (int i = 1; i <= n; i++) read(a[i]);    dp1[1023][1023] = 1;    for (int i = 1; i <= n; i++) {        for (int j = 0; j < 1024; j++) {            for (int S = j; S; S = (S - 1) & j)                dp[S][j] = 0;            dp[0][j] = 0;        }        for (int j = 0; j < 1024; j++) {            for (int S = j; S; S = (S - 1) & j) {                if (!dp1[S][j]) continue;                Add(dp[S][j], dp1[S][j]);                Add(dp[S & a[i]][j & a[i]], dp1[S][j]);                Add(dp[S & a[i]][j & a[i] | S], MOD - dp1[S][j]);            }            if (!dp1[0][j]) continue;            Add(dp[0][j], dp1[0][j]);            Add(dp[0][j & a[i]], dp1[0][j]);            Add(dp[0][j & a[i]], MOD - dp1[0][j]);        }        for (int j = 0; j < 1024; j++) {            for (int S = j; S; S = (S - 1) & j)                dp1[S][j] = dp[S][j];            dp1[0][j] = dp[0][j];        }    }    printf("%d\n", dp[0][0]);}