塔
顾名思义,试炼塔。放的是一些认为很好的题。不保证难度递增。
AT_agc001_f [AGC001F] Wide Swap
这可能是我至今见过最智慧的题了。令 \(Q_{P_i} = i\) 为 \(P_i\) 的逆排列。那么,在 \(P\) 上的一次交换(\(1 \le i < j \le n \land j - i \ge k \land |P_j - P_i| = 1\))等价于在 \(Q\) 上交换相邻两个数(\(|Q_i - Q_{i + 1}| \ge k\))。
原题令 \(P\) 字典序最小,考虑在 \(Q\) 上代表什么。\(Q_i = 1\) 代表 \(P_1 = i\),\(P\) 字典序最小必然意味着 \(P_1\) 最小,然后 \(P_2\) 最小,以此类推,也就是说 \(Q\) 中 \(1\) 最靠前,然后 \(2\) 最靠前,以此类推。注意,这并不是说 \(Q\) 字典序最小,例如 \(Q = \{3, 1, 2\}\) 和 \(Q = \{2, 3, 1\}\),后者字典序更小,但前者 \(1\) 更靠前。
于是,需要贪心地令 \(Q\) 中 \(1\) 最靠前,然后 \(2\) 最靠前,以此类推。如果 \(Q_i < Q_{i + 1}\),那么交换它们必然是不优的。于是只能对 \(Q_i > Q_{i + 1}\) 的交换。这是只有两个数的情况。现在扩展到有两个序列 \(A, B\),假设其内部都已经最优排序,现在需要将其合并。也就是归并排序。
假设指针分别在 \(i, j\),当且仅当 \(B_j + k\) 小于或等于 \(A\) 中 \(i\) 处的后缀最小值时,\(B_j\) 可以不断交换到 \(A_i\) 前面,即放 \(B_j\);否则放 \(A_i\)。
点击查看代码
const int N = 500005;
int n, k, a[N], b[N], minn[N];
void merge(int l, int r){
if(l == r) return ;
int mid = (l + r) >> 1;
merge(l, mid), merge(mid + 1, r);
int p = l, q = mid + 1, cnt = l;
minn[mid] = a[mid];
for(int i = mid - 1; i >= l; i--) minn[i] = min(minn[i + 1], a[i]);
while(p <= mid && q <= r){
if(a[q] + k <= minn[p]) b[cnt++] = a[q++];
else b[cnt++] = a[p++];
}
while(p <= mid) b[cnt++] = a[p++];
while(q <= r) b[cnt++] = a[q++];
for(int i = l; i <= r; i++) a[i] = b[i];
}
signed main(){
n = read(), k = read();
for(int i = 1; i <= n; i++) a[read()] = i;
merge(1, n);
for(int i = 1; i <= n; i++) b[a[i]] = i;
for(int i = 1; i <= n; i++) print(b[i]), putchar('\n');
return 0;
}
dmy 2026 CSP-S 模拟赛 Day 11 (Div 2) #C. 蜗蜗的反馈灯阵
点击查看题面
蜗蜗正在研究一组带有反馈控制器的指示灯。灯阵共有 \(n\) 盏灯,编号为 \(1\) 到 \(n\),每盏灯可以处于亮起或熄灭状态。
第 \(i\) 盏灯的控制器设有一个整数闭区间 \([l_i,r_i]\)。它只统计其他 \(n−1\) 盏灯中亮起的数量:如果这个数量在 \([l_i,r_i]\) 内,第 \(i\) 盏灯应当亮起;否则,它应当熄灭。
如果一种亮灭配置中的所有灯都符合各自控制器的要求,就称这种配置为稳定配置。对于整数 \(k\),若存在恰好有 \(k\) 盏灯亮起的稳定配置,就将 \(k\) 加入集合 \(S\)。因此,\(0 \le k \le n\),同一个 \(k\) 即使对应多种稳定配置,也只在 \(S\) 中计入一次。
蜗蜗给定了 \(n\) 个整数 \(d_1, d_2, \dots, d_n\)。第 \(i\) 个控制器的区间会从所有满足 \(0 \le l_i \le r_i \le d_i\) 的整数闭区间中等概率选择,各个控制器的选择相互独立。
请你求出随机选择所有区间后,集合 \(S\) 的大小的期望,答案对 \(998244353\) 取模。\(0 \le d_i < n \le 500\)。
这一块都是我自己的想法,赛时拿下 \(45\) 分,赛后改进拿下 \(70\) 分。
期望即为所有集合大小之和,除以区间选择的总方案数。总方案数为 \(\prod \frac{(d_i + 1) \times (d_i + 2)}{2}\),求所有集合大小之和。
拆贡献就是,枚举每个 \(k\),求它合法的方案数,累加起来就是所有集合大小之和。
考虑知道所有区间,判断 \(k\) 是否合法。
-
\(k = 0\),\((\sum [l_i = 0]) = 0\)。
-
\(k > 0\),\((\sum [l_i = k]) = 0 \ \operatorname{and}\ (\sum [l_i \le k - 1 \land k \le r_i(必亮)]) \le k \ \operatorname{and}\ (\sum [l_i > k \lor r_i < k - 1(必暗)]) \le n - k\)。
\(k = 0\) 要求每个 \(l_i, r_i\) 在 \([1, d_i]\) 里任选,方案数为 \(\prod \frac{d_i \times (d_i + 1)}{2}\)。
\(k > 0\) 考虑 DP。设 \(dp[j][a][b]\) 表示前 \(j\) 盏灯,\((\sum[l_i \le k - 1 \land k \le r_i]) = a, (\sum[l_i > k \lor r_i < k - 1]) = b\) 的方案数。
-
\(d_j < k - 1\):
\(dp[j - 1][a][b] \times \frac{(d + 1) \times (d + 2)}{2} \rightarrow dp[j][a][b + 1]\)
-
\(d_j = k - 1\):
\(dp[j - 1][a][b] \times k \rightarrow dp[j][a][b]\)
\(dp[j - 1][a][b] \times \frac{(k - 1) \times k}{2} \rightarrow dp[j][a][b + 1]\)
-
\(d_j = k\):
\(dp[j - 1][a][b] \rightarrow illegal\)
\(dp[j - 1][a][b] \times k \rightarrow dp[j][a][b]\)
\(dp[j - 1][a][b] \times k \rightarrow dp[j][a + 1][b]\)
\(dp[j - 1][a][b] \times \frac{(k - 1) \times k}{2} \rightarrow dp[j][a][b + 1]\)
-
\(d_j > k\):
\(dp[j - 1][a][b] \times (d - k + 1) \rightarrow illegal\)
\(dp[j - 1][a][b] \times k \rightarrow dp[j][a][b]\)
\(dp[j - 1][a][b] \times k \times (d - k + 1) \rightarrow dp[j][a + 1][b]\)
\(dp[j - 1][a][b] \times (\frac{(k - 1) \times k}{2} + \frac{(d - k) \times (d - k + 1)}{2}) \rightarrow dp[j][a][b + 1]\)
整合一下,就是如下三个式子:
\(dp[j - 1][a][b] \times k \times [d \ge k - 1] \rightarrow dp[j][a][b]\)
\(dp[j - 1][a][b] \times k \times (\max(d, k - 1) - k + 1) \rightarrow dp[j][a + 1][b]\)
\(dp[j - 1][a][b] \times (\frac{(\min(d, k - 2) + 1) \times (\min(d, k - 2) + 2)}{2} + \frac{(\max(d, k) - k) \times (\max(d, k) - k + 1)}{2}) \rightarrow dp[j][a][b + 1]\)
最终答案为 \(\sum dp[n][a \le k][b \le n - k]\)。暴力做的话是 \(O(n^4)\) 的,也是我的赛时做法,部分分 \(30\) 分。对于 \(\max d_i \le 1\) 发现,当 \(k = 1\) 时,\(b\) 只可能是 \(0\);否则,\(a\) 只可能是 \(0\)。此时转移是 \(O(n^3)\) 的,部分分 \(15\) 分,也是我赛时的全部分数。赛后发现 \(\max d_i \le 20\) 同理呀,当 \(k \le \max d_i\) 时暴力做;否则,\(a\) 只可能是 \(0\)。此时转移是 \(O(\max d_i n^3)\) 的,另有部分分 \(25\) 分,也是我认为我能得到的最高分 \(70\) 分。
正解是容斥,\(a \le k \land b \le n - k\) 的方案数,就是总方案数减 \(a > k\) 的方案数再减 \(b > n - k\) 的方案数。
设 \(f[j][i]\) 表示前 \(j\) 盏灯,\(a = i\) 方案数,\(g[j][i]\) 表示前 \(j\) 盏灯,\(b = i\) 方案数。再用 \(all\) 表示总方案数。转移是类似上面的。注意所有方案都要合法。时间复杂度 \(O(n^3)\)。
点击查看代码
int n, sum, ans, maxd, d[N];
int f[N][N], g[N][N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
n = read(), sum = 1ll, ans = 1ll;
for(int i = 1; i <= n; i++){
maxd = max(maxd, d[i] = read());
MUL(sum, d[i] * (d[i] + 1) / 2);
MUL(ans, inv((d[i] + 1) * (d[i] + 2) / 2));
}
for(int k = 1; k <= n; k++){
for(int j = 0; j <= n; j++)
for(int i = 0; i <= n; i++)
f[j][i] = g[j][i] = 0ll;
int all = f[0][0] = g[0][0] = 1ll;
for(int j = 1; j <= n; j++){
int A = k * (max(d[j], k - 1) - k + 1); // 必亮。
int B = (min(d[j], k - 2) + 1) * (min(d[j], k - 2) + 2) / 2 + (max(d[j], k) - k) * (max(d[j], k) - k + 1) / 2; // 必暗。
int C = k * (d[j] >= k - 1); // 随意。
for(int i = 0; i <= n; i++)
f[j][i] = (f[j - 1][i] * (B + C) + (i ? f[j - 1][i - 1] * A : 0)) % mod,
g[j][i] = (g[j - 1][i] * (A + C) + (i ? g[j - 1][i - 1] * B : 0)) % mod;
MUL(all, A + B + C);
}
for(int a = k + 1; a <= n; a++) ADD(all, mod - f[n][a]);
for(int b = n - k + 1; b <= n; b++) ADD(all, mod - g[n][b]);
ADD(sum, all);
}
cout << sum * ans % mod;
return 0;
}
P11149 [THUWC 2018] 城市规划
简述就是树上求有至多两种颜色的连通块数量。我当然是不会了,但是 Air 切了。
树、计数、连通块,可以考虑树形 DP。钦定树以 \(1\) 为根。为防止计重,令一个连通块只在其根节点处被计算。
考虑以点 \(u\) 为根的子树,由于要求连通块的颜色种类至多为二,其中一个必然是 \(a_u\),另一个写进 DP 状态里。
那么 DP 状态就很好想到了。设 \(dp_{u, c}\) 表示以 \(u\) 为根、除了 \(a_u\) 的另一种颜色为 \(c\) 的连通块数。特别的,当 \(c = a_u\) 时表示只有一种颜色。
状态数肯定是爆空间了,那就先写出来 \(O(n^2)\) 的暴力转移吧。
-
若 \(a_u \ne a_v\),发现只可能对 \(dp_{u, a_v}\) 产生贡献。\(dp'_{u, a_v} \leftarrow dp_{u, a_v} + (dp_{u, a_u} + dp_{u, a_v}) \times (dp_{v, a_v} + dp_{v, a_u})\)。
-
若 \(a_u = a_v\),枚举 \(x \ne a_u\)。\(dp'_{u, x} \leftarrow dp_{u, x} \times (1 + dp_{v, x} + dp_{v, a_v}) + dp_{u, a_u} \times dp_{v, x}\)。注意 \(dp_{u, a_u} \times dp_{v, a_v}\) 不能贡献到 \(dp'_{u, x}\) 上,而是应该贡献到 \(dp'_{u, a_u}\) 上,即 \(dp_{u, a_u} = dp_{u, a_u} \times (1 + dp_{v, a_v})\)。
初值显然是 \(dp_{u, a_u} = 1\),表示只有 \(u\) 的连通块。最后对答案贡献 \(\sum\limits_{x = 1}^n dp_{u, x}\)。
$O(n^2)$ 转移代码
int dp[5005][5005];
void dfs(int u, int father){
dp[u][a[u]] = 1;
for(int i = head[u]; i; i = o[i].nxt){
int v = o[i].ver;
if(v != father){
dfs(v, u);
if(a[u] != a[v]){
int A = dp[u][a[u]], B = dp[u][a[v]];
int C = dp[v][a[v]], D = dp[v][a[u]];
dp[u][a[v]] = (B + (A + B) * (C + D)) % mod;
}
else{
int A = dp[u][a[u]], B = dp[v][a[v]];
for(int x = 1; x <= n; x++) if(x ^ a[u]){
int U = dp[u][x], V = dp[v][x];
dp[u][x] = (U * (1 + V + B) + A * V) % mod;
}
dp[u][a[u]] = A * (1 + B) % mod;
}
}
}
for(int x = 1; x <= n; x++) if(dp[u][x]) ADD(ans, dp[u][x]);
}
对于 \(a_u \ne a_v\) 是 \(O(1)\) 转移,但是对于 \(a_u = a_v\) 的 \(O(n)\) 转移要怎么优化呢?
写成代码中的形式会比较自然地想到线段树合并。\(a_u \ne a_v\) 时,进行单点查询、单点修改;\(a_u = a_v\) 时,\(A, B\) 单点查询,\(dp_{u, x \ne a_u}\) 线段树合并,\(dp_{u, a_u}\) 单点修改。
难点在于合并,当 \(u\) 在这段区间的 \(dp\) 为空时,即代码中的 \(U = 0\),有 \(dp'_{u, x} \leftarrow dp_{u, a_u} \times dp_{v, x}\)。当 \(v\) 在这段区间的 \(dp\) 为空时,即代码中的 \(V = 0\),有 \(dp'_{u, x} \leftarrow dp_{u, x} \times (1 + dp_{v, a_v})\)。当都不为空时,继续递归,到叶子节点时就正常求 \(dp\) 值即可。
最后对答案的贡献就是全局和。因此线段树需要维护区间和、支持区间乘。
此外,建议先写一下 \(O(n^2)\) 的暴力 DP,然后对照着改成线段树合并。
点击查看代码
#define int long long
const int N = 500005, mod = 998244353;
int n, ans, a[N];
struct SON{
int ver, nxt;
}o[N << 1];
int tot, head[N];
inline void add_edge(int u, int v){
o[++tot] = SON{v, head[u]};
head[u] = tot;
}
inline void ADD(int &x, int y){
(x += y) >= mod && (x -= mod);
}
inline void MUL(int &x, int y){
(x *= y) >= mod && (x %= mod);
}
int dp[5005][5005];
void dfs(int u, int father){
dp[u][a[u]] = 1;
for(int i = head[u]; i; i = o[i].nxt){
int v = o[i].ver;
if(v != father){
dfs(v, u);
if(a[u] != a[v]){
int A = dp[u][a[u]], B = dp[u][a[v]];
int C = dp[v][a[v]], D = dp[v][a[u]];
dp[u][a[v]] = (B + (A + B) * (C + D)) % mod;
}
else{
int A = dp[u][a[u]], B = dp[v][a[v]];
for(int x = 1; x <= n; x++) if(x ^ a[u]){
int U = dp[u][x], V = dp[v][x];
dp[u][x] = (U * (1 + V + B) + A * V) % mod;
}
dp[u][a[u]] = A * (1 + B) % mod;
}
}
}
for(int x = 1; x <= n; x++) if(dp[u][x]) ADD(ans, dp[u][x]);
}
struct segment_tree{
int tot, rt[N];
struct node{
int ls, rs;
int lz = 1;
int sum;
}o[N * 50];
#define ls(x) o[x].ls
#define rs(x) o[x].rs
#define lz(x) o[x].lz
#define sum(x) o[x].sum
inline void push_down(int x){
if(lz(x) != 1){
MUL(lz(ls(x)), lz(x));
MUL(sum(ls(x)), lz(x));
MUL(lz(rs(x)), lz(x));
MUL(sum(rs(x)), lz(x));
lz(x) = 1;
}
}
inline void push_up(int x){
sum(x) = sum(ls(x));
ADD(sum(x), sum(rs(x)));
}
void change(int &x, int l, int r, int t, int val){
if(!x) x = ++tot;
if(l == r){
sum(x) = val;
return ;
}
push_down(x);
int mid = (l + r) >> 1;
if(t <= mid) change(ls(x), l, mid, t, val);
else change(rs(x), mid + 1, r, t, val);
push_up(x);
}
int ask(int x, int l, int r, int t){
if(!x) return 0;
if(l == r) return sum(x);
push_down(x);
int mid = (l + r) >> 1;
if(t <= mid) return ask(ls(x), l, mid, t);
else return ask(rs(x), mid + 1, r, t);
}
void merge(int &x, int y, int l, int r, int A, int B){
if(!x){
x = y;
MUL(lz(x), A);
MUL(sum(x), A);
return ;
}
if(!y){
MUL(lz(x), 1 + B);
MUL(sum(x), 1 + B);
return ;
}
if(l == r){
int U = sum(x), V = sum(y);
sum(x) = (U * (1 + V + B) + A * V) % mod;
return ;
}
push_down(x);
push_down(y);
int mid = (l + r) >> 1;
merge(ls(x), ls(y), l, mid, A, B);
merge(rs(x), rs(y), mid + 1, r, A, B);
push_up(x);
}
}tree;
#define rt(x) tree.rt[x]
void DP(int u, int father){
tree.change(rt(u), 1, n, a[u], 1);
for(int i = head[u]; i; i = o[i].nxt){
int v = o[i].ver;
if(v != father){
DP(v, u);
if(a[u] != a[v]){
int A = tree.ask(rt(u), 1, n, a[u]), B = tree.ask(rt(u), 1, n, a[v]);
int C = tree.ask(rt(v), 1, n, a[v]), D = tree.ask(rt(v), 1, n, a[u]);
tree.change(rt(u), 1, n, a[v], (B + (A + B) * (C + D)) % mod);
}
else{
int A = tree.ask(rt(u), 1, n, a[u]), B = tree.ask(rt(v), 1, n, a[v]);
tree.merge(rt(u), rt(v), 1, n, A, B);
tree.change(rt(u), 1, n, a[u], (A * (1 + B)) % mod);
}
}
}
ans = (ans + tree.sum(rt(u))) % mod;
}
signed main(){
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int i = 1; i < n; i++){
int u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
// dfs(1, 0);
DP(1, 0);
print(ans);
return 0;
}
P15762 [JAG 2025 Summer Camp #1] Inside Yamanote
下文所有变量的角标都对 \(n\) 取模,如 \(i + 1\) 代表 \((i + 1) \bmod n\)。
设首都为 \(pos\),每个点到首都的距离为 \(d_i\),若满足 \(|d_{i + 1} - d_i| \le l_i\) 且 \(d_i \ge 0\) 且存在 \(d_i = 0\),则必然存在一种合法的建边方案。后两个条件分别代表距离非负、存在首都;第一个条件是因为可以让 \(pos\) 向 \(i(i \ne pos)\) 连边,边权为 \(d_i\),当且仅当满足 \(|d_{i + 1} - d_i| \le l_i\) 时,\(pos, i, i + 1\) 在几何平面上构成三角形(或三点共线),则 \(pos\) 到 \(i\) 的距离为 \(d_i\)。
最后要求的声誉,是所有 \(d_{X_i} - d_{Y_i}\) 的总和。考虑每个 \(d_i\) 的贡献,必然是一个系数乘上 \(d_i\),设其为 \(s_i\)。那么每次让 \(s_{X_i}\) 加一,\(s_{Y_i}\) 减一即可。由于每次都加一个、减一个,必然有 \(\sum\limits_{i = 0}^{n - 1} s_i = 0\)。
则答案为:\(\sum\limits_{i = 0}^{n - 1} s_i \times d_i\)。
然后就是人类智慧了。发现对于一种只满足 \(|d_{i + 1} - d_i| \le l_i\) 的 \(d_i\) 方案,必然可以令其变成合法方案(即变得同时满足之前所说的三个条件)。因为令 \(c = -\min d_i\),令全体 \(d_i\) 加上 \(c\),则此时最小的 \(d_i\) 变成 \(0\),其余 \(d_i\) 同样 \(\ge 0\),同时仍然满足 \(|d_{i + 1} - d_i| \le l_i\)(因为都加上同一个值,差还是不变)。
于是可以抛开首都,\(d_i\) 方案只需要满足 \(|d_{i + 1} - d_i| \le l_i\),求 \(\sum\limits_{i = 0}^{n - 1} s_i \times d_i\) 的最大值。
\(d_i\) 还是有点难入手。令 \(w_i = d_{i + 1} - d_i\),则 \(|w_i| \le l_i \Longleftrightarrow -l_i \le w_i \le l_i\)。抛开 \(d_i\),只构造 \(w_i\) 的合法方案,仍然有对应的 \(d_i\) 方案。因此可以从 \(w_i\) 入手。显然还需要有 \(\sum\limits_{i = 0}^{n - 1} w_i = 0\),因为要满足环状。
推式子:
设 \(a_j = \sum\limits_{i = j + 1}^{n - 1} s_i\),\(a_i\) 是不变的。则答案为 \(\sum\limits_{i = 0}^{n - 1} w_i \times a_i\)。
因此,问题转化为:求满足 \(-l_i \le w_i \le l_i\) 且 \(\sum\limits_{i = 0}^{n - 1} w_i = 0\) 的 \(w_i\) 方案中,\(\sum\limits_{i = 0}^{n - 1} w_i \times a_i\) 的最大值。
先抛开 \(\sum\limits_{i = 0}^{n - 1} w_i = 0\) 的限制。由于 \(a_i\) 有正有负,肯定是贪心地令 \(a_i < 0\) 的 \(w_i = -l_i,a_i \ge 0\) 的 \(w_i = l_i\),设该答案为 \(A\)。
然后考虑这个限制。设 \(neg\) 表示 \(a_i < 0\) 的 \(-w_i\) 之和,\(pos\) 表示 \(a_i \ge 0\) 的 \(w_i\) 之和。
若 \(a_i < 0\) 的 \(w_i\) 加一,会使答案减少 \(-a_i\),该 \(w_i\) 可以从 \(-l_i\) 加到 \(l_i\),最多加 \(2 \times l_i\) 次。
若 \(a_i \ge 0\) 的 \(w_i\) 减一,会使答案减少 \(a_i\),该 \(w_i\) 可以从 \(l_i\) 减到 \(-l_i\),最多减 \(2 \times l_i\) 次。
如果 \(neg > pos\),希望选择一些 \(a_i < 0\) 的 \(w_i\) 增加一些值,增加的总次数为 \(k = neg - pos\)。
等价于有 \(2 \times l_i\) 个 \(-a_i(a_i < 0)\),前 \(k\) 小的数之和为 \(B\),则答案为 \(A - B\)。
如果 \(neg \le pos\),希望选择一些 \(a_i \ge 0\) 的 \(w_i\) 减少一些值,减少的总次数为 \(k = pos - neg\)。
等价于有 \(2 \times l_i\) 个 \(a_i(a_i \ge 0)\),前 \(k\) 小的数之和为 \(B\),则答案为 \(A - B\)。
开权值线段树,以 \(a_i\) 为下标(或者 \(-a_i\))。线段树上二分求前 \(k\) 小之和即可。注意 \(0 \le a_i \le m\),线段树开到 \(m\) 不是 \(n\)。
点击查看代码
const int N = 200005;
int n, m, q;
int l[N], s[N], a[N];
struct segment_tree{
int sum[N << 2], cnt[N << 2];
#define ls x << 1
#define rs x << 1 | 1
void add(int x, int l, int r, int t, int c, int val){
if(l == r){
sum[x] += c * val;
cnt[x] += c;
return ;
}
int mid = (l + r) >> 1;
if(t <= mid) add(ls, l, mid, t, c, val);
else add(rs, mid + 1, r, t, c, val);
sum[x] = sum[ls] + sum[rs];
cnt[x] = cnt[ls] + cnt[rs];
}
int ask(int x, int l, int r, int k){
if(l == r) return min(cnt[x], k) * l;
int mid = (l + r) >> 1;
if(k <= cnt[ls]) return ask(ls, l, mid, k);
else return sum[ls] + ask(rs, mid + 1, r, k - cnt[ls]);
}
}NEG, POS;
int A, B;
int neg, pos;
inline void update(int i, int k){
if(a[i] < 0){
NEG.add(1, 0, m, -a[i], k * 2 * l[i], -a[i]);
A += k * a[i] * (-l[i]);
neg += k * l[i];
}
else{
POS.add(1, 0, m, a[i], k * 2 * l[i], a[i]);
A += k * a[i] * l[i];
pos += k * l[i];
}
}
signed main(){
n = read(), m = read(), q = read();
for(int i = 0; i < n; i++) l[i] = read();
for(int i = 0; i < m; i++){
int x = read(), y = read();
++s[x], --s[y];
}
for(int i = n - 2; i >= 0; i--) a[i] = a[i + 1] + s[i + 1];
for(int i = 0; i < n; i++) update(i, 1);
for(; q; q--){
int i = read();
update(i, -1);
l[i] = read();
update(i, 1);
if(neg > pos) B = NEG.ask(1, 0, m, neg - pos);
else B = POS.ask(1, 0, m, pos - neg);
print(A - B), putchar('\n');
}
return 0;
}
十一校联考 asdfz 2026 NOIP 模拟赛 #C. 深搜
点击查看题面
对于一个有根树,我们记 \(f(v)\) 为对以点 \(v\) 为根的子树 DFS(可以以任意顺序访问子节点)能够得到的本质不同的“节点遍历序列去除非前缀最大值后形成的序列”的数量对 \(10^{9} + 7\) 取模后的结果。
给定一个 \(n\) 个点的,编号从 \(1\) 开始且以点 \(r\) 为根的树。共 \(t\) 组测试数据:
-
当 \(o = 1\) 时,请求出 \(f(r)\);
-
当 \(o = 2\) 时,请求出 \(f(1), f(2), \dots, f(n)\)。
对于所有数据,\(o \in \{1,2\}, t \le 10^{4}, n \le 5 \times 10^{5}, \sum n \le 2 \times 10^{6}\)。
\(f(u)\) 本质上是对以点 \(u\) 为根的子树 DFS(可以以任意顺序访问子节点)能够得到的本质不同的“前缀最大值序列”的数量。
设 \(\text{subtree}(u)\) 表示以点 \(u\) 为根的子树内的点编号的集合,\(sz_u\) 表示 \(\text{subtree}(u)\) 的大小。
设 \(m_u = \max\limits_{v \in \text{subtree}(u)} v\) 表示 \(\text{subtree}(u)\) 的最大值。若 \(m_u\) 没有作为前缀最大值,则 \(\text{subtree}(u)\) 内不会有任何点编号作为前缀最大值。
设 \(V = \{v_1, v_2, \dots, v_k\}\) 为点 \(u\) 的子节点编号集合。进入点 \(v_i\) 并遍历完 \(\text{subtree}(v_i)\) 后,若再进入 \(m_{v_j} < m_{v_i}\) 的点 \(v_j\),则 \(m_{v_j}\) 不会作为前缀最大值,\(\text{subtree}(v)\) 不会对 \(f(u)\) 产生贡献。因此,将 \(v_i\) 按 \(m_{v_i}\) 从大到小排序,依次考虑 \(v_i\)。若要让 \(\text{subtree}(v_i)\) 对 \(f(u)\) 产生贡献,需要将 \(v_i\) 放置在最靠前的位置,即优先进入点 \(v_i\)。
然后考虑求贡献。比较显然的 DP 状态是,设 \(f_{u,i}\) 表示之前的前缀最大值为 \(i\),然后进入点 \(u\),\(\text{subtree}(u)\) 内部的本质不同的“前缀最大值序列”的数量。那么,按照如上所述,设考虑到 \(v\),对于 \(i \ge u\) 的转移:\(f'_{u, i} = f_{u, i} + f_{v, i} \times f_{u, m_v}\);对于 \(i < u\) 的转移:\(f'_{u, i} = f'_{u, u}\)。暴力 DP 是 \(O(n^2)\) 的。答案为 \(f_{u, u}\)。
设 \(g_{u, i} = f_{u, i} - f_{u, i + 1}\),对于 \(i \ge u\) 的转移:
对于 \(i < u\) 的转移:\(g'_{u, i} = 0\)。
这样,\(g_u\) 中有值的位置不会超过 \(sz_u\) 个,就可以上线段树合并了,需要支持区间求和(求 \(\sum\limits_{j = m_v}^n g_{u, j}\))、区间乘(乘 \(\sum\limits_{j = m_v}^n g_{u, j}\))、区间推平(推平为 \(0\),改为区间乘 \(0\) 也行)。答案为 \(\sum g_{u, i}\)。
点击查看代码
#define int long long
inline int read(){
int x;
cin >> x;
return x;
}
const int N = 500005, mod = 1000000007;
int test, type, T;
int n, r;
struct SON{
int ver, nxt;
}o[N << 1];
int tot, head[N];
inline void add_edge(int u, int v){
o[++tot] = SON{v, head[u]};
head[u] = tot;
}
int m[N];
void dfs(int u, int father){
m[u] = u;
for(int i = head[u]; i; i = o[i].nxt){
int v = o[i].ver;
if(v != father) dfs(v, u), m[u] = max(m[u], m[v]);
}
}
inline void ADD(int &x, int y){
(x += y) >= mod && (x -= mod);
}
inline void MUL(int &x, int y){
(x *= y) >= mod && (x %= mod);
}
struct segment_tree{
int node_tot, root[N];
struct node{
int ls, rs;
int sum, lz;
}o[N * 20];
#define ls(x) o[x].ls
#define rs(x) o[x].rs
#define sum(x) o[x].sum
#define lz(x) o[x].lz
inline void push_down(int x){
if(lz(x) != 1){
if(ls(x)) MUL(sum(ls(x)), lz(x)), MUL(lz(ls(x)), lz(x));
if(rs(x)) MUL(sum(rs(x)), lz(x)), MUL(lz(rs(x)), lz(x));
lz(x) = 1;
}
}
inline void push_up(int x){
if(ls(x)) sum(x) = sum(ls(x));
else sum(x) = 0;
if(rs(x)) ADD(sum(x), sum(rs(x)));
}
void build(int &x, int l, int r, int t, int val){
x = ++node_tot;
sum(x) = val, lz(x) = 1;
ls(x) = rs(x) = 0;
if(l == r) return ;
int mid = (l + r) >> 1;
if(t <= mid) build(ls(x), l, mid, t, val);
else build(rs(x), mid + 1, r, t, val);
}
int ask(int x, int l, int r, int tl, int tr){
if(!x) return 0;
if(tl <= l && r <= tr) return sum(x);
push_down(x);
int mid = (l + r) >> 1, res = 0;
if(tl <= mid) ADD(res, ask(ls(x), l, mid, tl, tr));
if(tr > mid) ADD(res, ask(rs(x), mid + 1, r, tl, tr));
return res;
}
void mul(int x, int l, int r, int tl, int tr, int val){
if(!x) return ;
if(tl <= l && r <= tr){
MUL(sum(x), val);
MUL(lz(x), val);
return ;
}
push_down(x);
int mid = (l + r) >> 1;
if(tl <= mid) mul(ls(x), l, mid, tl, tr, val);
if(tr > mid) mul(rs(x), mid + 1, r, tl, tr, val);
push_up(x);
}
void del(int x, int l, int r, int tl, int tr){
if(!x) return ;
if(tl <= l && r <= tr){
sum(x) = 0, lz(x) = 1;
ls(x) = rs(x) = 0;
x = 0;
return ;
}
push_down(x);
int mid = (l + r) >> 1;
if(tl <= mid) del(ls(x), l, mid, tl, tr);
if(tr > mid) del(rs(x), mid + 1, r, tl, tr);
push_up(x);
}
void merge(int &x, int y, int l, int r){
if(!x){
x = y;
return ;
}
if(!y) return ;
if(l == r){
ADD(sum(x), sum(y));
return ;
}
push_down(x);
push_down(y);
int mid = (l + r) >> 1;
merge(ls(x), ls(y), l, mid);
merge(rs(x), rs(y), mid + 1, r);
push_up(x);
}
#undef ls
#undef rs
#undef sum
#undef lz
}tree;
#define root(x) tree.root[x]
int ans[N];
void DP(int u, int father){
vector<int> son;
for(int i = head[u]; i; i = o[i].nxt){
int v = o[i].ver;
if(v != father) DP(v, u), son.push_back(v);
}
if(u == m[u]){
tree.build(root(u), 1, n, u, 1);
ans[u] = 1;
return ;
}
sort(son.begin(), son.end(), [](const int &i, const int &j){
return m[i] > m[j];
});
for(auto v : son){
if(v == son[0]) root(u) = root(v);
else{
int val = tree.ask(root(u), 1, n, m[v], n);
tree.mul(root(v), 1, n, 1, n, val);
tree.merge(root(u), root(v), 1, n);
}
}
if(u - 1) tree.del(root(u), 1, n, 1, u - 1);
ans[u] = tree.ask(root(u), 1, n, 1, n);
}
inline void solve(){
n = read(), r = read();
tot = 0, tree.node_tot = 0;
for(int u = 1; u <= n; u++) head[u] = root(u) = 0;
for(int i = 1; i < n; i++){
int u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
dfs(r, 0);
DP(r, 0);
if(type == 1) cout << ans[r] << '\n';
else{
for(int i = 1; i < n; i++) cout << ans[i] << ' ';
cout << ans[n] << '\n';
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
for(test = read(), type = read(), T = read(); T; T--) solve();
return 0;
}
浙公网安备 33010602011771号