Divide and Conquer
Divide and Conquerhttps://qoj.ac/contest/4113/problem/20239
来源: 2026 icpc网络预选赛2 D题
题目概述
给定一棵深度为 \(n\) 的完美二叉树(线段树),叶子节点数量为 \(M = 2^n\),节点总数 \(N = 2^{n+1} - 1\)。叶子节点的值必须是 \(1,2,\dots,M\) 的一个排列。每个内部节点的值等于其两个子节点值的最大值。现在有 \(q\) 个约束 \((u_i, x_i)\),要求节点 \(u_i\) 的值恰好为 \(x_i\)。求满足所有约束的叶子排列数量,对 \(998244353\) 取模。
关键观察
- 叶子值是一个排列:每个值 \(1 \dots M\) 恰好出现一次。
- 内部节点的值是其子树中叶子值的最大值。
- 若节点 \(u\) 被约束为 \(x\),则:
- \(u\) 子树中所有叶子值 \(\le x\);
- \(u\) 子树中至少有一个叶子值恰好为 \(x\);
- \(u\) 的祖先节点值 \(\ge x\)。
预处理与合法性检查
1. 根节点约束
根节点(节点 1)的值必然是 \(M\)(所有叶子中的最大值)。若题目已给根节点约束且不是 \(M\),直接无解。我们将根节点的约束设为 \(M\)。
2. 计算子树叶子数 \(sz[u]\)
- 叶子节点(\(u \ge M\)):\(sz[u] = 1\);
- 内部节点(\(u < M\)):\(sz[u] = sz[2u] + sz[2u+1]\)。
3. 计算每个节点的值上限 \(up[u]\)
从根向下传递:
- \(up[1] = M\);
- 若节点 \(u\) 有约束 \(x\),则 \(up[u] = x\),并且必须满足 \(x \le up[\text{parent}]\);
- 若无约束,则 \(up[u] = up[\text{parent}]\)。
同时检查:对每个有约束的节点 \(u\),必须满足 \(sz[u] \le x \le M\)。
4. 同一节点多个约束
若同一节点给出不同的 \(x\),无解。
5. 相同约束值的节点必须形成祖先-后代链
因为值 \(x\) 只能出现一次,如果多个节点都约束为 \(x\),它们必须位于同一条从祖先到后代的路径上。否则,两个不相交的子树都要求最大值为 \(x\),矛盾。
对于每个值 \(v\),记录约束为 \(v\) 的最深节点 \(deepest[v]\)。
计数原理:从大到小分配值
我们按值从大到小(\(v = M\) 到 \(1\))依次为每个值选择一个叶子来放置。每一步的选择数相乘,即为总方案数。
1. 计算 \(free[u]\)
\(free[u]\) 表示节点 \(u\) 子树中,\(up\) 值恰好等于 \(up[u]\) 的叶子数量。
计算方法:
- 叶子节点:\(free[u] = 1\);
- 内部节点:\(free[u] = \sum_{v \in \{2u, 2u+1\}} free[v] \cdot [up[v] == up[u]]\)。
用途:当某个约束要求节点 \(u\) 的值为 \(x\)(即 \(up[u] = x\))时,值 \(x\) 必须由 \(u\) 子树中某个叶子承担,而只有 \(up\) 恰好等于 \(x\) 的叶子才能取到 \(x\)。所以 \(free[u]\) 就是能承担这个约束值 \(x\) 的候选叶子数。
2. 计算全局 \(cnt[v]\) 与后缀和 \(suf[v]\)
- \(cnt[v]\):整棵树中 \(up\) 值等于 \(v\) 的叶子数量。
- \(suf[v] = \sum_{k \ge v} cnt[k]\):所有 \(up \ge v\) 的叶子总数,即有能力承担值 \(v\) 的叶子数量。
3. 从大到小分配
维护变量 \(used\),表示已经分配了值的叶子数量。
对于当前值 \(v\):
-
情况 A:\(v\) 是某个约束节点的值(即 \(deepest[v] \ne -1\))
- 值 \(v\) 必须分配给 \(deepest[v]\) 子树中 \(up == v\) 的叶子。
- 可选叶子数为 \(free[deepest[v]]\)。
- 方案数乘上 \(free[deepest[v]]\)。
- \(used \gets used + 1\)。
-
情况 B:\(v\) 不是任何约束节点的值
- 值 \(v\) 可以分配给任何 \(up \ge v\) 且尚未被分配值的叶子。
- 这些叶子的总数是 \(suf[v]\)。
- 已经分配了 \(used\) 个叶子,它们都满足 \(up \ge v\),所以剩余可用叶子数为 \(suf[v] - used\)。
- 方案数乘上 \((suf[v] - used)\)。
- \(used \gets used + 1\)。
最后将所有方案数相乘,并对 \(998244353\) 取模。
正确性说明
- 从大到小分配,保证大值优先占用那些“只有大 \(up\) 才能承担”的叶子。
- 对于约束值 \(v\),只有 \(up\) 恰好等于 \(v\) 的叶子能取 \(v\),因此必须从 \(free[deepest[v]]\) 中选一个。
- 对于非约束值 \(v\),任何 \(up \ge v\) 的未分配叶子都可以取 \(v\),因为它们的上限允许,且不会影响其他约束。
- 每一步的选择都是独立的,直接相乘即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 998244353;
void solve() {
int n, q; cin >> n >> q;
int N = (1 << (n + 1)) - 1;//总数
int M = (1 << n);//叶子结点数量
vector<int> limit(N + 1, -1); limit[1] = M;
bool repeat = false;
for(int i = 0; i < q; i ++) {
int u, x; cin >> u >> x;
if(limit[u] != -1 && limit[u] != x) repeat = true;
limit[u] = x;
}
if(repeat) {cout << "0\n"; return;}
vector<int> sz(N + 1, 1);
vector<int> dep(N + 1, 0);
for(int i = M - 1; i >= 1; i --) sz[i] = sz[i * 2] + sz[i * 2 + 1];
for(int i = 2; i <= N; i ++) dep[i] = dep[i / 2] + 1;
//合法性检查
vector<int> up(N + 1, 0); up[1] = limit[1];
for(int u = 2; u <= N; u ++) {
int p = u / 2;
up[u] = up[p];
if(limit[u] != -1) {
if(limit[u] > up[p]) {cout << "0\n"; return;}
if(sz[u] > limit[u]) {cout << "0\n"; return;}
up[u] = limit[u];
}
}
vector<vector<int>> group(N + 1);
for(int i = 1; i <= N; i ++) {
if(limit[i] != -1) group[limit[i]].push_back(i);
}
vector<int> deepest(M + 2, -1);//每个值的最深出现位置
for(int i = 1; i <= M; i ++) {
if(group[i].empty()) continue;
sort(group[i].begin(), group[i].end());
for(size_t j = 0; j < group[i].size() - 1; j ++) {
int anc = group[i][j];
int des = group[i][j + 1];
while(des > anc) des /= 2;
if(des != anc) {cout << "0\n"; return;}
}
deepest[i] = group[i].back();
}
vector<int> free(N + 1, 0);//每个up[u]有多少个叶子承担
for(int i = M; i <= N; i ++) free[i] = 1;
for(int i = M - 1; i >= 1; i --) {
if(up[i] == up[i * 2]) free[i] += free[i * 2];
if(up[i] == up[i * 2 + 1]) free[i] += free[i * 2 + 1];
}
vector<int> cnt(M + 1, 0);//每个up值的叶子数量
for(int i = M; i <= N; i ++) cnt[up[i]] ++;
vector<int> suf(M + 2, 0);//计算后缀up值
for(int i = M; i >= 1; i --) suf[i] = suf[i+1] + cnt[i];
ll ans = 1;
ll used = 0;
for(int v = M; v >= 1; v --) {
if(deepest[v] != -1) {
int d = deepest[v];
if(free[d] == 0) {//没有多余叶子可以承载
ans = 0; break;
}
ans = ans * free[d] % MOD;
used ++;
}
else {//v只能放到up[u]>=v的叶子上
int avail = suf[v] - used;
if(avail <= 0) {
ans = 0; break;
}
ans = ans * avail % MOD;
used ++;
}
}
cout << ans << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int _ = 1; //cin >> _;
while(_--) solve();
return 0;
}

浙公网安备 33010602011771号