题解:P12136 [蓝桥杯 2025 省 B] 生产车间
Untitled_unrevised · · 题解
能意识到 G 有多难就能意识到 G 有多简单。
本文将介绍 P12136 [蓝桥杯 2025 省 B] 生产车间 与子集和问题之间的关联,并给出基于此得出的做法,以及可能的时间优化方式。
题意简述
给定一棵有根树,忽略若干个节点使得对于剩下的非叶节点
题目分析
树上问题,自然想到要用树形 dp 来做。然后注意到数据范围
这意味着,或许,这个题很难,使得更低复杂度的做法可能是不存在的。但到底有多难?我们可以拿另一个已经有明确难度的题来比较。
与子集和问题的联系
考虑一种比较极端的数据:除了
如果你解决了该问题,那么通过判定
综上,该问题是严格不弱于子集和问题的,如果
小值域子集和问题
现在应该就能看出
到了这里,应该就能够得出
- 对于每一个叶节点
u ,它有选与不选两种选择,从而所有可能的子集和为S_u = \{0, w_u\} ; - 对于每一个枝节点
v ,从它所有子节点可能的子集和计算出自己可能的的子集和然后除去\gt w_v 的,具体而言:
其中
最后对于
可能的优化
尽管
可以使用 C++ 的 std::bitset 来优化常数,此时单次合并子集和可以优化到 std::bitset 所用的字长,一般为 32 或 64。总时间复杂度
另一种更狠的优化方式超出了本题的难度范围,本人赛场上懒得想了就直接写了这个:使用 FFT 优化单次合并子集和,可以做到
给定子集和
从而
而计算
参考代码
#include <algorithm>
#include <array>
#include <iostream>
#include <vector>
using std::cin;
using std::cout;
using std::vector;
using u64 = unsigned long long;
constexpr u64 P = 998'244'353;
template<class Grp, class Exp, class Ty>
Ty pow(Grp base, Exp exp, Ty id) {
for(; exp; exp >>= 1, base *= base)
if(exp & 1)
id *= base;
return id;
}
template<class Grp, class Exp, class Ty>
Ty& powass(Grp base, Exp exp, Ty &id) {
for(; exp; exp >>= 1, base *= base)
if(exp & 1)
id *= base;
return id;
}
struct Fp {
u64 x;
constexpr Fp(u64 val = 0) : x(val) {}
Fp& operator+=(const Fp &other) {
x += other.x;
if(x >= P) x -= P;
return *this;
}
Fp& operator-=(const Fp &other) {
if(x < other.x) x += P;
x -= other.x;
return *this;
}
Fp& operator*=(const Fp &other) {
x = x * other.x % P;
return *this;
}
Fp& operator/=(const Fp &other) {
return powass(other, P - 2, *this);
}
friend Fp operator+(Fp lhs, const Fp &rhs) { return lhs += rhs; }
friend Fp operator-(Fp lhs, const Fp &rhs) { return lhs -= rhs; }
friend Fp operator*(Fp lhs, const Fp &rhs) { return lhs *= rhs; }
friend Fp operator/(Fp lhs, const Fp &rhs) { return lhs /= rhs; }
};
Fp prim(3);
Fp coprim = Fp(1) / prim;
vector<u64> graph[1001];
vector<u64> subset[1001];
bool visited[1001] = {};
u64 w[1001] = {};
template<class RanIt>
void FFT(RanIt arr, bool type) {
for(int logseg = 0; logseg < 11; ++logseg) {
size_t seg = 1ull << logseg;
Fp omega = pow(type ? coprim : prim, (P - 1) >> (logseg + 1), Fp(1));
for(RanIt lhs = arr; lhs != arr + 2048; lhs += (seg + seg)) {
RanIt rhs = lhs + seg;
Fp phi = Fp(1);
for(size_t off = 0; off < seg; ++off) {
Fp g = lhs[off];
Fp hw = rhs[off] * phi;
lhs[off] = g + hw;
rhs[off] = g - hw;
phi *= omega;
}
}
}
if(type) {
Fp div = Fp(1) / Fp(2048);
for(size_t i = 0; i < 2048; ++i)
arr[i] *= div;
}
}
vector<u64> subsetmerge(u64 u, const vector<u64> &lset, const vector<u64> &rset) {
if(lset.size() * rset.size() < 2048) {
bool contains[2001] = {};
for(u64 x : lset) {
for(u64 y : rset) {
contains[x + y] = true;
}
}
vector<u64> res;
for(u64 z = 0; z <= w[u]; ++z)
if(contains[z])
res.push_back(z);
return res;
}
std::array<Fp, 2048> lff{}, rff{};
for(u64 x : lset) lff[x] = Fp(1);
for(u64 x : rset) rff[x] = Fp(1);
FFT(lff.begin(), false);
FFT(rff.begin(), false);
for(size_t i = 0; i < 2048; ++i)
lff[i] *= rff[i];
FFT(lff.begin(), true);
vector<u64> res;
for(u64 z = 0; z <= w[u]; ++z)
if(lff[z].x)
res.push_back(z);
return res;
}
void dfs(u64 u) {
visited[u] = true;
bool isleaf = true;
for(u64 v : graph[u]) {
if(!visited[v]) {
isleaf = false;
dfs(v);
subset[u] = subsetmerge(u, subset[u], subset[v]);
}
}
if(isleaf) {
subset[u].push_back(w[u]);
}
}
int main() {
size_t n;
cin >> n;
for(u64 u = 1; u <= n; ++u) {
cin >> w[u];
subset[u].push_back(0);
}
for(u64 i = 1; i < n; ++i) {
u64 u, v;
cin >> u >> v;
graph[u].push_back(v);
graph[v].push_back(u);
}
dfs(1);
cout << subset[1].back();
return 0;
}