题解:P16310 [ICPC 2023 Jinan R] 开灯 2
很有意思的题。
根据题述条件,不难看出一个合法的点亮灯泡的方案相当于一个极大独立集。也就是说,我们要构造一个 \(m\) 条边、最大度数为 \(d\) 的无向连通图,使得图中的极大独立集个数最大化。
我们指出,使得极大独立集个数最大化的图,一定可以取一棵树。
证明
使用调整法。
任取一个极大独立集个数最大的无向连通图 \(G\),取环上一条边 \((u,v)\) 将其删去,新增一个点 \(x\),添加边 \((u,x)\),得到新图 \(G'\)。
显然 \(G'\) 依旧是连通图。而边数保持不变,\(deg_u\) 不变,\(deg_v\) 减少 \(1\),\(deg_x=1\),因此最大度数不增。
任取 \(G\) 中的一个极大独立集 \(S\)。分类讨论:
- \(u\notin S\):取 \(S'=S\cup\{x\}\)。因为 \(x\) 只与 \(u\) 相连,所以 \(S'\) 是 \(G'\) 的独立集。进一步验证极大性:\(u\) 有已选邻点 \(x\);对于 \(v\),若 \(v\notin S\),那么 \(v\) 在 \(G\) 中必然有非 \(u\) 的已选邻点。因此 \(S'\) 也是 \(G'\) 的极大独立集。
- \(u\in S\),\(v\) 有非 \(u\) 的已选邻点:取 \(S'=S\)。显然 \(S'\) 是 \(G'\) 的独立集。进一步验证极大性:\(x\) 有已选邻点 \(u\);\(v\) 有非 \(u\) 的已选邻点。因此 \(S'\) 也是 \(G'\) 的极大独立集。
- \(u\in S\),\(v\) 只有 \(u\) 这个已选邻点:取 \(S'=S\cup\{v\}\)。因为 \((u,v)\) 被删除,所以在 \(G'\) 中,选择 \(v\) 满足独立性。进一步验证极大性:\(x\) 有已选邻点 \(u\)。因此 \(S'\) 是 \(G'\) 的极大独立集。
于是调整后,\(G'\) 的极大独立集大小不减。不断将 \(G\) 调整成一棵树即可。\(\Box\)
问题转化为,求 \(m+1\) 个点、最大度数为 \(d\) 的树中,极大独立集数量的最大值。
不妨先考虑对于一棵树,我们怎么求出极大独立集数量。
树形 DP,对于 \(sub_u\),设 \(f_{u,0/1/2}\) 表示满足条件的方案数,其中三种状态的定义分别为:
- \(f_{u,0}\):\(u\) 被选中。
- \(f_{u,1}\):\(u\) 未被选中,但存在已选的儿子节点。
- \(f_{u,2}\):\(u\) 未被选中,没有已选的儿子节点,也就是父亲节点必须被选中。
叶子节点的状态为 \(f_{u,0}=1,f_{u,1}=0,f_{u,2}=1\)。
考虑加入一个儿子节点 \(v\) 时如何合并:
最终答案即为 \(f_{rt,0}+f_{rt,1}\)。
我们并不知道树的具体结构,因此要在对树结构 DP 的同时,加入上面的 DP 状态。
考虑一些很暴力的想法:设 \(h_{e,p}\) 表示考虑一棵 \(e\) 条边的有根树,根节点至多有 \(p\) 个儿子,所有点的度数不超过 \(d\),此时所有可能的三元组 \((f_{u,0},f_{u,1},f_{u,2})\) 构成的集合。
设临时状态 \(dp_{i,j}\) 表示当前根节点连了 \(i\) 个儿子,子树内总共有 \(j\) 条边,此时所有可能的三元组构成的集合。刷表转移,枚举新的儿子中的边数 \(k\),满足 \(j+k+1\leq e\),然后将 \(dp_{i,j},h_{k,d-1}\) 中的三元组两两合并后加入到 \(dp_{i+1,j+k+1}\) 中。注意这里三元组的合并方式,就是前面的 DP 中的合并方式。
得到 \(dp_{i,j}\) 后,我们有
最终答案即为
不难看出,实际上我们只需要对 \(h_{e,d-1/d}\) 做 DP。
但是这样三元组个数太多了,大概率过不了。
考虑支配思想,观察到对于两个三元组 \((f_0,f_1,f_2),(f_0',f_1',f_2')\),若 \(f_0\geq f_0'\land f_1\geq f_1'\land f_2\geq f_2'\),则 \((f_0,f_1,f_2)\) 支配 \((f_0',f_1',f_2')\),可以删去后者。在转移过程中进行剪枝,剔除无用状态即可。
还需要构造方案。对于每个状态 \((f_0,f_1,f_2)\),我们额外记录儿子节点状态 \((k,id)\) 构成的集合,其中 \(k\) 表示儿子子树中的边数,\(id\) 表示这棵子树对应 \(g_{k,d-1}\) 中的第 \(id\) 个状态。递归构造即可。
实际实现时可以按 \(d\) 分组,每次固定 \(d\) 做 DP,然后回答所有对应的测试数据。
粗略地分析时间复杂度。设剪枝后集合的最大大小为 \(S\)。固定 \(e\),剪枝前集合的大小最坏可以达到 \(\mathcal{O}(eS^2)\),因此这部分剪枝的复杂度为 \(\mathcal{O}(\min(d,e)e^3S^4)\),DP 的转移复杂度则为 \(\mathcal{O}(\min(d,e)e^3S^2)\)。对于单个 \(d\),总的时间复杂度为 \(\mathcal{O}(dm^4S^4)\)。单次查询的时间复杂度为 \(\mathcal{O}(S+m)\)。可以验证 \(S=125\),且实际实现完全达不到上界,可以通过。
代码挺好写的。
主要代码
struct State {
int a, b, c;
vector<pii> son;
};
vector<State> f[MAXM], g[MAXM], dp[MAXM][MAXM];
vector<State> comp(vector<State> vec) {
sort(vec.begin(), vec.end(), [&](const State &lhs, const State &rhs) {
if (lhs.a != rhs.a) return lhs.a < rhs.a;
else if (lhs.b != rhs.b) return lhs.b < rhs.b;
else return lhs.c < rhs.c;
});
vec.erase(unique(vec.begin(), vec.end(), [&](const State &lhs, const State &rhs) {
return lhs.a == rhs.a && lhs.b == rhs.b && lhs.c == rhs.c;
}), vec.end());
int n = vec.size();
vector<bool> del(n);
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (vec[i].b <= vec[j].b && vec[i].c <= vec[j].c) {
del[i] = true;
break;
}
}
}
vector<State> res;
for (int i = 0; i < n; ++i)
if (!del[i])
res.emplace_back(move(vec[i]));
return res;
}
void solve(int d) {
// f: d, g: d - 1
for (int i = 0; i < MAXM; ++i) {
f[i].clear();
g[i].clear();
}
f[0].push_back({1, 0, 1});
g[0].push_back({1, 0, 1});
for (int e = 1; e < MAXM; ++e) {
int mxDeg = min(d, e), mxDeg2 = min(d - 1, e);
for (int i = 0; i <= mxDeg; ++i)
for (int j = 0; j <= e; ++j)
dp[i][j].clear();
dp[0][0].push_back({1, 0, 1});
for (int i = 0; i < mxDeg; ++i) {
for (int j = 0; j <= e; ++j) {
if (dp[i][j].empty()) continue;
for (int k = 0; k <= e - j - 1; ++k) {
const auto &vec = g[k];
for (const auto &cur : dp[i][j]) {
for (int id = 0; id < vec.size(); ++id) {
const auto &[a, b, c, _] = vec[id];
State nxt = {
cur.a * (b + c),
cur.b * (a + b) + cur.c * a,
cur.c * b
};
nxt.son = cur.son;
nxt.son.emplace_back(k, id);
dp[i + 1][j + k + 1].emplace_back(move(nxt));
}
}
}
}
for (int j = 0; j <= e; ++j)
if (!dp[i + 1][j].empty())
dp[i + 1][j] = comp(move(dp[i + 1][j]));
}
vector<State> cur;
for (int i = 0; i <= mxDeg; ++i) {
for (const auto &st : dp[i][e]) cur.emplace_back(st);
if (!cur.empty()) cur = comp(move(cur));
if (i == mxDeg2) g[e] = cur;
if (i == mxDeg) f[e] = cur;
}
}
}
pair<int, vector<pii>> query(int m) {
pii mx = {0, 0};
for (int i = 0; i < f[m].size(); ++i)
chkMax(mx, pii{f[m][i].a + f[m][i].b, i});
vector<pii> T;
int cnt = 1;
auto dfs = [&](auto &&self, int u, int faU, const State &cur) -> void {
if (faU) T.emplace_back(faU, u);
for (auto [e, id] : cur.son) self(self, ++cnt, u, g[e][id]);
};
dfs(dfs, 1, 0, f[m][mx.second]);
return {mx.first, move(T)};
}

浙公网安备 33010602011771号