Loading

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;
}
posted @ 2026-07-23 15:47  Trent900  阅读(23)  评论(1)    收藏  举报