目录
一、题目
1、题目描述
编辑
2、输入输出
2.1输入
编辑
2.2输出
编辑
3、原题链接
https://www.luogu.com.cn/problem/P3067
二、解题报告
1、思路分析
洛谷题解区要么就是 O(3^n) 的错误做法,要么就是 O(6^{n / 2}) 的做法
这里给一种比较稳的做法
考虑一个平衡子集 S,它可以被分为4部分:
-
s1:在 a 的前半部分中属于A 的集合
-
s2:在 a 的前半部分中属于B 的集合
-
s3:在 a 的后半部分中属于A 的集合
-
s4:在 a 的后半部分中属于B 的集合
那么一个合法子集一定满足:s1 + s3 = s2 + s4 => s1 - s2 = s4 - s3
那么我们折半枚举前后两部分所有子集的 sum|A'| - sum|B'|
对于前半部分,我们枚举所有子集,保存所有 <sum|A'| - sum|B'|, msk> 这样的二元数对(其中msk为选择的下标集合),然后按照第一维排序并按照二元数对去重
然后按 第一维分组,用bitset 合并msk,保存在列表 st中
对于后半部分,我们枚举子集s,计算出sum|A'| - sum|B'|后,在st中二分得到对应的msk,和子集s的bitset合并
对于后半部分的子集s,其 bitset 中1的个数就是前半部分和s合并后是平衡子集的合法子集个数
值得注意的是,我们折半枚举子集的时候把空集也包含上,这样计算出来的答案会多1,输出的时候减去即可。
2、复杂度
时间复杂度: O(3 ^ {n/2} * (log(3^{n/2}) + 2^{n/2} / 64))空间复杂度:O(3^{n / 2} * 32)
3、代码详解
cpp
#include <bits/stdc++.h>
namespace ranges = std::ranges;
namespace views = std::views;
using i64 = long long;
using u32 = unsigned;
using u64 = unsigned long long;
constexpr int B = 1024;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
const int U = 1 << (n / 2);
std::vector<int> a(n / 2), b(n - n / 2), sum(U);
for (int i = 0; i < n / 2; ++i) {
std::cin >> a[i];
sum[1 << i] = a[i];
}
for (int i = 0; i < n - n / 2; ++i) {
std::cin >> b[i];
}
std::vector<std::array<int, 2>> p;
for (int s = 0; s < U; ++s) {
if (s > 0) sum[s] = sum[s & (s - 1)] + sum[s & -s];
p.push_back({sum[s], s});
for (int t = s; t > 0; t = (t - 1) & s) {
p.push_back({sum[s] - sum[t] * 2, s});
}
}
ranges::sort(p);
p.resize(std::unique(p.begin(), p.end()) - p.begin());
std::vector<std::pair<int, std::bitset<B>>> st;
for (int i = 0, j = 0; i < p.size(); i = j) {
std::bitset<B> msk;
while (j < p.size() && p[i][0] == p[j][0]) {
msk.set(p[j++][1]);
}
st.push_back({p[i][0], msk});
}
const int V = 1 << b.size();
std::vector<std::bitset<B>> good(V);
std::vector<int> nsum(V);
for (int i = 0; i < b.size(); ++i) {
nsum[1 << i] = b[i];
}
for (int s = 0; s < V; ++s) {
if (s > 0) nsum[s] = nsum[s & (s - 1)] + nsum[s & -s];
int target = nsum[s];
auto it = ranges::lower_bound(st, target, {}, [](auto &x) {
return x.first;
});
if (it != st.end() && it->first == target) {
good[s] |= it->second;
}
for (int t = s; t > 0; t = (t - 1) & s) {
target = nsum[s] - nsum[t] * 2;
it = ranges::lower_bound(st, target, {}, [](auto &x) {
return x.first;
});
if (it != st.end() && it->first == target) {
good[s] |= it->second;
}
}
}
int ans = 0;
for (auto &bt : good) {
ans += bt.count();
}
std::cout << ans - 1 << '\n';
return 0;
}

编辑
编辑
编辑