1. 程式人生 > 實用技巧 >【01揹包】B001_AW_異或和是質數的子集數(上限分析+滾動陣列優化記憶體)

【01揹包】B001_AW_異或和是質數的子集數(上限分析+滾動陣列優化記憶體)

給出 n 個互不相同的正整數。
問存在多少個子集,使得子集中所有數的異或和是質數。
由於答案可能很大,請你輸出對 10^9+7 取模後的結果。

輸入格式
第一行包含整數 n。
第二行包含 n 個正整數。
輸出格式
輸出一個整數,表示滿足條件的子集數量對 109+7 取模後的結果。
資料範圍
1≤n≤5000,
1≤ 給定正整數 ≤5000。

輸入樣例:
3
1 2 3
輸出樣例:
4

方法一:dp

子集是無重複元素的,故可看成 01 揹包

  • 定義狀態
    • f[i][j] 表示從前 i 個數中選出若干個數,異或和 j 的方案數
      - tip:由於異或是不進位的加法,故 n 個數異或的結果不會超過最大數,最大數為 5000,比 \(2^{12}\)
      大一點(\(log_{k}5000/log_{k}2\)),但我不太清楚 5000 的二進位制最高位的 1 在第幾位,為了保險這裡第二維直接取 \(2^{13}\)
  • 思考初始化:
    • f[...][...]=0,f[0][0]=1
  • 思考狀態轉移方程
    • f[i][j]|=f[i-1][j^A[i]],if (j^A[i] < M)
  • 思考輸出:f[n][j],j∈[2,M],if (isPrime(j))
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7, M=pow(2, 13)+5;
bool st[M];
void inti() {
    memset(st, true, sizeof st);
    for (int i=2; i<M; i++) if (st[i])
    for (int j=i*i; j<M; j+=i) 
        st[j]=false;
}
int main() {
    std::ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);

    int n; cin>>n; inti();
    int A[n+1], f[2][M];
    for (int i=1; i<=n; i++) cin>>A[i]; 
    memset(f, 0, sizeof f); f[0][0]=1;
    
    int k=1;
    for (int i=1; i<=n; i++) {
        for (int j=0; j<M; j++) {
            f[k][j]=f[k^1][j]; int nx=A[i]^j;
            if (nx<M) 
                f[k][j]=(f[k][j]+f[k^1][nx])%mod; 
        }
        k^=1;
    }
    int ans=0;
    for (int j=2; j<M; j++) if (st[j])
        ans=(ans+f[k^1][j])%mod;
    cout << ans;
    return 0;
}