[BZOJ4762]最小集合/[JZOJ5151]幻夢終醒

題目大意

給定n 個數ai ,你要從中選出一個非空子集,使得這個子集AND 和是0 ,並且這個子集的任意一個非空子集的AND 和都不是0

1n103,0ai<210


題目分析

又是一道好題。
在範老師@AwD的博客看懂的,大家可以過去膜拜一下。
思路是這樣的:令SaS 刪去a 之後形成的集合,令f(S) 表示S 集合中所有元素的AND 和。
那麼答案就是

S[(f(S)=0)aS(f(Sa)0)]

這個看起來不太好算,我們對後面的部分容斥一下,變成
SSS[(f(S)=0)aS(f(Sa)=0)](1)|S|

其實就是
SSS[(f(S)=0)(aSf(Sa)=0)](1)|S|

這個怎麼計算呢?令fi,k,j 表示做到了第i 個位置,f(S)=kaSf(Sa)=j 的方案數(注意這個是乘上容斥係數的)。
顯然必須要滿足kj ,也就是說狀態數只有O(n310)
轉移的話,假設第i+1 個數是d ,那麼有三種轉移:
  不選dfi+1,k,jfi,k,j
 S 中選擇了d ,但是S 中沒有選擇:fi+1,kd,jdfi,k,j
 S 以及S 中都選擇了dfi+1,kd,(jd)kfi,k,j
初始狀態f0,2101,2101=1 ,目標狀態fn,0,0
時間複雜度O(n310)

代碼實現

#include <iostream>
#include <cctype>
#include <cstdio>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int P=1000000007;
const int N=1005;
const int S=1024;

int f[2][S][S];
int a[N];
int n;

void update(int &x,int y){x=(x+y)%P;}

int dp()
{
    f[0][S-1][S-1]=1;
    for (int i=0;i<n;++i)
    {
        int cur=i&1,nxt=cur^1,x=a[i+1];
        for (int j=0;j<S;++j)
        {
            for (int k=j;k;k=(k-1)&j) f[nxt][k][j]=0;
            f[nxt][0][j]=0;
        }
        for (int j=0;j<S;++j)
        {
            for (int k=j;k;k=(k-1)&j) update(f[nxt][k][j],f[cur][k][j]),update(f[nxt][k&x][j&x],f[cur][k][j]),update(f[nxt][k&x][k|j&x],P-f[cur][k][j]);
            update(f[nxt][0][j],f[cur][0][j]);
        }
    }
    return f[n&1][0][0];
}

int main()
{
    freopen("never.in","r",stdin),freopen("never.out","w",stdout);
    n=read();
    for (int i=1;i<=n;++i) a[i]=read();
    printf("%d\n",dp());
    fclose(stdin),fclose(stdout);
    return 0;
}
發佈了227 篇原創文章 · 獲贊 42 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章