CF449D Jzzhu and Numbers 题解
首先看到这个,背包吧。
\(f[i][j]\) 代表以 \(i\) 结尾的,与和为 \(j\) 的情况数。
我们发现光是状态就存不下,能不能搞掉一维?
思考通过组合计数搞掉 \(i\) 这一维度。我们令 \(f[j]\) 代表与和为 \(j\) 的超集的所有状况。
我们可以通过 SOSDP 快速找出 \(j\) 的超集有多少个数。然后 \(2 ^ {cnt} - 1\) 就是 \(f[j]\) 了。
然后我们算完个数了之后,再把答案还原回去,就是做一个刚刚 SOSDP 的逆操作,即差分,就可以求出与和恰好为 \(j\) 的情况了~
这个过程,太简洁了。容斥 DP 的应用还是相当广泛的。
// Problem: D. Jzzhu and Numbers
// Contest: Codeforces - Codeforces Round 257 (Div. 1)
// Memory Limit: 256 MB
// Time Limit: 2000 ms
#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;
const int N = 1e6 + 7, U = (1 << 20) - 1;
const int MOD = 1e9 + 7;
int n, a[N], f[U + 7];
int qpow(int x, int y){
int res = 1;
while(y){
if(y & 1) (res *= x) %= MOD;
y >>= 1, (x *= x) %= MOD;
} return res;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> a[i];
f[a[i]] ++;
}
for(int i = 0; i < 20; i ++){
for(int j = U; j >= 0; j --){
if(!(j & (1 << i))) (f[j] += f[j + (1 << i)]) %= MOD;
}
}
for(int j = 0; j <= U; j ++) f[j] = qpow(2, f[j]) - 1;
for(int i = 0; i < 20; i ++){
for(int j = U; j >= 0; j --){
if(!(j & (1 << i))) (f[j] -= f[j + (1 << i)]) %= MOD;
}
}
cout << (f[0] + MOD) % MOD << "\n";
return 0;
}

浙公网安备 33010602011771号