[NOI2024] 登山
此题的核心在于选择正确的 dp 转移顺序。
首先有个 \(\mathcal{O}(n^2)\) 的暴力 dp。设 \(f_{i}\) 表示从 \(i\) 号点出发的方案数。转移考虑枚举子树中的点 \(j\) 表示从 \(i\) 休息到 \(j\),再从 \(j\) 冲刺到一个点 \(k\),\(k\) 用前缀和优化。注意到 \(d_k<d_j\),一个合法路径必然由若干冲刺划分的段构成,所以计数不重不漏。
此时的转移顺序为 \(i,j,k\),为填表法,我们需要设计一种优秀的更新方式,降低复杂度。
考虑调整转移顺序,使用刷表法,按照 \(d\) 从小到大枚举 \(k\),\(k\) 只会更新子树内的值,使得 \(j\) 合法的 \(k\) 必然为一段区间,那么合法的 \(j\) 可以动态维护。我们称使 \(j\) 合法的操作为 “激活”,使 \(j\) 不合法的操作为 “失活”,那么 \(j\) 只会在一个点上激活,一个点上失活,操作数量为 \(\mathcal{O}(n)\),可以接受。再考虑 \(j\) 如何更新 \(i\),要求路径 \((i,j)\) 上的所有点 \(u\),满足 \(d_u-h_u> d_k\)。如果满足该条件,则保留,否则删除。考虑动态维护保留的点,由于 \(d_k\) 非严格递增,那么一个点必然在一段前缀保留,后缀删除,操作数量为 \(\mathcal{O}(n)\),也可以接受。
现在可以动态维护所有点的状态,考虑如何更新。如果把 \(f_k\) 放在所有 \(j\) 上,那么 \(i\) 需要查询子树连通块的值的和,不好做。考虑调整修改方式,不把 \(k\) 贡献给 \(j\),直接将 \(k\) 贡献给 \(i\)。对每个点 \(i\) 维护系数 \(c\) 代表目前子树中有 \(c\) 个 \(j\) 可以贡献给 \(i\),那么每次 \(k\) 的更新,可以对子树内所有 \(i\),进行操作 \(v_i\leftarrow v_i+c_if_k\)。考虑动态维护 \(c_i\),分讨操作:激活 \(j\),那么找到深度最小的合法祖先 \(\text{anc}\),\(\forall u\in(\text{anc},j),c_u\leftarrow c_u+1\);失活 \(j\),那么同理,找到深度最小的合法祖先 \(\text{anc}\),\(\forall u\in(\text{anc},j),c_u\leftarrow c_u-1\);删除 \(j\),注意此时 \(j\) 必然处于失活状态,那么令 \(k=c_j\),找到深度最小的合法祖先 \(\text{anc}\),\(\forall u\in(\text{anc},j),c_u\leftarrow c_u-k\)。
上述操作都可以用树剖和线段树维护。
注意标记的维护:\(c\) 表示系数,\(t\) 表示系数乘的权值,\(tag\) 表示另外加上的权值,一个点的 \(f\) 为 \(ct+tag\)。
标记合并考虑钦定先 \(c\) 后 \(t\),后面加 \(c\) 就交换顺序,把新增的权值加到 \(tag\) 上去。
#include <bits/stdc++.h>
#define ll long long
#define vi vector
#define pb push_back
using namespace std;
bool START;
const int N = 1e5 + 5, mod = 998244353;
void add(int & x, int y) {x += y; if (x >= mod) x -= mod;}
int pls(int x, int y) {x += y; if (x >= mod) x -= mod; return x;}
void dec(int & x, int y) {x += mod - y; if (x >= mod) x -= mod;}
int sub(int x, int y) {x += mod - y; if (x >= mod) x -= mod; return x;}
int mul(int x, int y) {return 1ll * x * y % mod;}
int cid, t_c, n;
int fa[N], l[N], r[N], h[N], d[N];
int sn[N], sz[N], idx, dfn[N], dy[N], tp[N], f[N];
vi <int> G[N];
struct opt {
int op, x;
};
vi <opt> vc[N];
namespace smt {
int c[N << 2], t[N << 2], tag[N << 2], ex[N << 2];
#define ls(p) (p << 1)
#define rs(p) ((p << 1) | 1)
void workc(int k, int p) {
add(c[p], k), dec(tag[p], mul(k, t[p]));
}
void workt(int k, int p) {
add(t[p], k);
}
void worktag(int k, int p) {
add(tag[p], k);
}
void psd(int p) {
if (c[p]) workc(c[p], ls(p)), workc(c[p], rs(p)), c[p] = 0;
if (t[p]) workt(t[p], ls(p)), workt(t[p], rs(p)), t[p] = 0;
if (tag[p]) worktag(tag[p], ls(p)), worktag(tag[p], rs(p)), tag[p] = 0;
}
void psu(int p) {
ex[p] = ex[ls(p)] + ex[rs(p)];
}
void bld(int l, int r, int p) {
c[p] = t[p] = tag[p] = 0;
if (l == r) return ex[p] = 1, void();
int mid = (l + r) >> 1; bld(l, mid, ls(p)), bld(mid + 1, r, rs(p)), psu(p);
}
void addc(int l, int r, int L, int R, int k, int p) {
if (l >= L && r <= R) return workc(k, p);
int mid = (l + r) >> 1; psd(p);
if (L <= mid) addc(l, mid, L, R, k, ls(p));
if (R > mid) addc(mid + 1, r, L, R, k, rs(p));
}
void addt(int l, int r, int L, int R, int k, int p) {
if (l >= L && r <= R) return workt(k, p);
int mid = (l + r) >> 1; psd(p);
if (L <= mid) addt(l, mid, L, R, k, ls(p));
if (R > mid) addt(mid + 1, r, L, R, k, rs(p));
}
int askv(int l, int r, int ps, int p) {
if (l == r) return pls(mul(c[p], t[p]), tag[p]);
int mid = (l + r) >> 1; psd(p);
return ps <= mid ? askv(l, mid, ps, ls(p)) : askv(mid + 1, r, ps, rs(p));
}
int askc(int l, int r, int ps, int p) {
if (l == r) return c[p];
int mid = (l + r) >> 1; psd(p);
return ps <= mid ? askc(l, mid, ps, ls(p)) : askc(mid + 1, r, ps, rs(p));
}
void mdf(int l, int r, int ps, int k, int p) {
if (l == r) return ex[p] = k, void();
int mid = (l + r) >> 1; psd(p);
ps <= mid ? mdf(l, mid, ps, k, ls(p)) : mdf(mid + 1, r, ps, k, rs(p));
psu(p);
}
int find(int l, int r, int L, int R, int p) {
if (ex[p] == r - l + 1) return 0;
if (l == r) return dy[l];
int mid = (l + r) >> 1; psd(p);
if (R > mid) {
int t = find(mid + 1, r, L, R, rs(p));
if (t) return t;
}
if (L <= mid) return find(l, mid, L, R, ls(p));
return 0;
}
}
using namespace smt;
bool END;
void init() {
for (int i = 1; i <= n; ++i) G[i].clear(), vc[i].clear();
for (int i = 1; i <= n; ++i) sn[i] = 0;
idx = 0;
}
void dfs(int x) {
sz[x] = 1;
for (auto y : G[x]) {
dfs(y), sz[x] += sz[y];
if (sz[sn[x]] < sz[y]) sn[x] = y;
}
}
void dfs2(int x, int TP) {
dfn[x] = ++idx, dy[idx] = x, tp[x] = TP;
if (sn[x]) dfs2(sn[x], TP);
for (auto y : G[x]) if (y ^ sn[x]) dfs2(y, y);
}
int anc(int x, int k) {
while (x) {
if (dfn[x] - dfn[tp[x]] < k)
k -= (dfn[x] - dfn[tp[x]] + 1), x = fa[tp[x]];
else {
return dy[dfn[x] - k];
}
}
return 0;
}
void act(int x, int k) {
while (x) {
int pos = find(1, n, dfn[tp[x]], dfn[x], 1);
if (!pos) addc(1, n, dfn[tp[x]], dfn[x], k, 1), x = fa[tp[x]];
else {
addc(1, n, dfn[pos] + 1, dfn[x], k, 1);
return;
}
}
}
void del(int x) {
int k = mod - askc(1, n, dfn[x], 1), X = x;
while (x) {
int pos = find(1, n, dfn[tp[x]], dfn[x], 1);
if (!pos) addc(1, n, dfn[tp[x]], dfn[x], k, 1), x = fa[tp[x]];
else {
addc(1, n, dfn[pos] + 1, dfn[x], k, 1);
break;
}
}
mdf(1, n, dfn[X], 0, 1);
}
void dfs3(int x) {
for (auto t : vc[x]) {
int op = t.op, u = t.x;
if (op == 1) act(u, 1);
else if (op == 2) act(u, mod - 1);
else del(u);
}
int val = (x == 1 ? 1 : askv(1, n, dfn[x], 1));
addt(1, n, dfn[x], dfn[x] + sz[x] - 1, val, 1);
f[x] = val;
for (auto y : G[x]) dfs3(y);
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> cid >> t_c;
while (t_c--) {
cin >> n, d[1] = 0;
init();
for (int i = 2; i <= n; ++i)
cin >> fa[i] >> l[i] >> r[i] >> h[i], d[i] = d[fa[i]] + 1, G[fa[i]].pb(i);
dfs(1), dfs2(1, 1);
bld(1, n, 1);
for (int i = 2; i <= n; ++i) {
h[i]++;
if (h[i] > r[i]) vc[anc(i, h[i] - 1)].pb((opt) {3, i});
else if (h[i] >= l[i])
vc[anc(i, r[i])].pb((opt) {1, i}), vc[anc(i, h[i] - 1)].pb((opt) {2, i}), vc[anc(i, h[i] - 1)].pb((opt) {3, i});
else
vc[anc(i, r[i])].pb((opt) {1, i}), vc[anc(i, l[i] - 1)].pb((opt) {2, i}), vc[anc(i, h[i] - 1)].pb((opt) {3, i});
}
dfs3(1);
for (int i = 2; i <= n; ++i) cout << f[i] << ' ';
cout << endl;
}
return 0;
}

浙公网安备 33010602011771号