IOI 2025 中国国家集训队互测做题记录
| 场次 | \(\quad\qquad\text{A}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{B}\qquad\quad\) | 完成情况 | \(\quad\qquad\text{C}\qquad\quad\) | 完成情况 |
|---|---|---|---|---|---|---|
| \(\text{Round 1}\) | 基础 ABC 练习题 | \(\color{green}\checkmark\) | 基础 01? 练习题 | \(\color{green}\checkmark\) | 基础 01 练习题 | |
| \(\text{Round 2}\) | 生命的循环 | \(\color{green}\checkmark\) | 树上简单求和 | \(\color{green}\checkmark\) | 路径计数 | |
| \(\text{Round 3}\) | 环上排序信息最优分割 | 研心 | 无限地狱 | |||
| \(\text{Round 4}\) | Désive | 分道扬镳 | 观虫我 | |||
| \(\text{Round 5}\) | 长野原龙势流星群 | \(\color{green}\checkmark\) | Classical Counting Problem | 运筹帷幄 | ||
| \(\text{Round 6}\) | 树数叔术 | \(\color{green}\checkmark\) | 欧伊昔 | 人间应又雪 | ||
| \(\text{Round 7}\) | Cyberangel | 新生舞会 | PM 大师 | |||
| \(\text{Round 8}\) | 熟练 | 皮鞋的多项式 | 幽默还是夢 | |||
| \(\text{Round 9}\) | 游戏 | 木桶效应 | 月亮的背面是粉红色的 | |||
| \(\text{Round 10}\) | 计算几何 | 分治 | 骨牌覆盖 | |||
| \(\text{Round 11}\) | 联通块 | 轮盘赌游戏 | 序列 | \(\color{green}\checkmark\) | ||
| \(\text{Round 12}\) | (LIS, LDS) - Coordinates | 签到题 | S>a<M | |||
| \(\text{Round 13}\) | 线段树与区间加 | 字符串 | 格雷码 | |||
| \(\text{Round 14}\) | Two permutations | 冲刺 | 串联 | |||
| \(\text{Round 15}\) | 药水 | 字符游戏 | \(\color{green}\checkmark\) | 子集和 | ||
| \(\text{Round 16}\) | 数位 DP | 数据结构 | \(\color{green}\checkmark\) | problem | ||
| \(\text{Round 17}\) | 链覆盖 | 又一个欧拉数问题 |
\(\color{blue}\mathbf{Round\;1}\)
基础 ABC 练习题 - 方心童
我们观察到对于一个 \(\texttt{A,B,C}\) 各出现 \(n\) 次的字符串 \(s\),其被划分出的 \(\texttt{ABC}\) 个数至少为 \(A=\max\limits_{1\le i\le 3n}a_i-c_i\),同理,\(\texttt{BCA}\) 个数至少为 \(B=\max\limits_{1\le i\le 3n}b_i-a_i\),\(\texttt{CAB}\) 个数至少为 \(C=\max\limits_{1\le i\le 3n}c_i-b_i\)。
故对于给定的 \(x,y,n-x-y\),\(s\) 存在划分方案的必要条件为 \(x\ge A,y\ge B,n-x-y\ge C,A+B+C\le n\),其中 \(a_i,b_i,c_i\) 分别表示前 \(i\) 个字母中 \(\texttt{A/B/C}\) 的出现次数。
充分性的证明
别急
故可以注意到当 \(s\) 确定时,符合条件的 \((x,y)\) 在平面直角坐标系上构成了一个斜三角形或空集,我们所求即为 \(s\) 的个数使得此区域与区域 \(\{(x,y)\mid x\in S_1,y\in S_2\}\) 有交。
由于该区域没有空洞,所以可以使用欧拉公式进行容斥做到 \(\mathcal{O}(n^5)\),此容斥也可以使用钦定斜三角形内最靠近原点的点做到同样的复杂度,但是具有更小的常数,参考实现中使用了后者。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 65;
unsigned int f[2][N][N][4];
signed main() {
int T; scanf("%d%*d", &T);
while (T--) {
int n; scanf("%d", &n);
string S1, S2; cin >> S1 >> S2;
vector<int> o1, o2;
for (int i = 0; i <= n; ++i) {
if (S1[i] == '1') o1.push_back(i);
if (S2[i] == '1') o2.push_back(i);
}
string S; cin >> S, S = ' ' + S;
if (n != 60) {
puts("-1");
continue;
}
unsigned int res = 0;
for (int i = 0; i < (int)o1.size(); ++i) {
for (int j = 0; j < (int)o2.size(); ++j) {
int l1 = (!i ? 0 : o1[i - 1] + 1), r1 = o1[i],
l2 = (!j ? 0 : o2[j - 1] + 1), r2 = o2[j], r3 = n - r1 - r2;
if (r3 < 0) continue;
memset(f[0], 0, sizeof(f[0]));
f[0][0][0][(!l1) | ((!l2) << 1)] = 1;
for (int k = 1; k <= n * 3; ++k) {
memset(f[k & 1], 0, sizeof(f[k & 1]));
for (int p = 0; p < k && p <= n; ++p) {
for (int q = 0; p + q <= k && q <= n && q - p <= r2; ++q) {
unsigned int *tg = f[(k & 1) ^ 1][p][q];
int r = k - 1 - p - q;
if (r < 0 || r > n || r - q > r3 || p - r > r1) continue;
if ((S[k] == '?' || S[k] == 'A') && p - r + 1 <= r1) {
unsigned int *tf = f[k & 1][p + 1][q];
if (p - r + 1 >= l1) tf[1] += tg[0] + tg[1], tf[3] += tg[2] + tg[3];
else tf[0] += tg[0], tf[1] += tg[1], tf[2] += tg[2], tf[3] += tg[3];
}
if ((S[k] == '?' || S[k] == 'B') && q - p + 1 <= r2) {
unsigned int *tf = f[k & 1][p][q + 1];
if (q - p + 1 >= l2) tf[2] += tg[0] + tg[2], tf[3] += tg[1] + tg[3];
else tf[0] += tg[0], tf[1] += tg[1], tf[2] += tg[2], tf[3] += tg[3];
}
if ((S[k] == '?' || S[k] == 'C') && r - q + 1 <= r3) {
unsigned int *tf = f[k & 1][p][q];
tf[0] += tg[0], tf[1] += tg[1], tf[2] += tg[2], tf[3] += tg[3];
}
}
}
}
res += f[n & 1][n][n][3];
}
}
printf("%u\n", res);
}
return 0;
}
基础 01? 练习题 - 郑钧
论文题。题解中给出了如下结论:
- 一个字符串 \(S(|S|>1)\) 是 TM 序列的子串当且仅当存在 \(S=A+B\) 使得 \(\operatorname{rev}(A),B\) 均为 TM 序列或其按位 flip 后的串的前缀(下称这种串为 TM 串)。
- 若一个字符串和其反串均为 TM 串,则该字符串长度为 \(2\) 的非负整数次次幂。
推论:若 \(S=A+BC=AB+C,(|A|,|B|,|C|\ge1)\) 且 \(\operatorname{rev}(A),BC,\operatorname{rev}(AB),C\) 均为 TM 串,则 \(B\) 长度为 \(2\) 的非负整数次次幂。 - 在字符串 \(S\) 的所有拆分方式 \(S=A+B\) 中,\(\operatorname{rev}(A),B\) 均为 TM 串的方案数不超过 \(2\)。
- \(S=A+BC=AB+C,(|A|,|B|,|C|\ge 1)\) 且两种拆分方式均合法当且仅当 \(|A|\le|B|,|C|\le|B|\) 且 \(\operatorname{rev}(AB),BC\) 均为 TM 串。
证明可参见原文,在此略去。故可对合法的拆分方式计数并减去情况 \(4\),这样需要考虑的串的个数是 \(\mathcal{O}(n\log n)\) 的,结合上简单计数原理并使用线段树维护历史和可简单处理区间询问,总时间复杂度为 \(\mathcal{O}(n\log^2n+m\log n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5, Q = 2e5 + 5, P = 998244353;
int qpow(int x, int y = P - 2) {
int res = 1;
while (y) {
if (y & 1) res = 1ll * res * x % P;
x = 1ll * x * x % P;
y >>= 1;
}
return res;
}
int n, q;
string S;
vector<int> pre[N], nxt[N];
int dp[N][2];
void solve() {
for (int i = 1; i <= n + 1; ++i) dp[i][0] = dp[i][1] = 0;
for (int i = n; i >= 1; --i) {
for (int j = 0; j < 2; ++j) {
if (S[i] == '?' || S[i] == '0' + j) dp[i][j] = i;
else dp[i][j] = i - 1;
if (dp[i][j] == i - 1) continue;
for (int k = 0; dp[i][j] == i + (1 << k) - 1; ++k) {
if (dp[dp[i][j] + 1][j ^ 1] - dp[i][j] >= (1 << k)) dp[i][j] += (1 << k);
}
int lz = j ^ 1;
for (int k = 19; ~k; --k) {
if (dp[i][j] > (1 << k) && dp[dp[i][j] + 1][lz] - dp[i][j] >= (1 << k)) dp[i][j] += (1 << k), lz ^= 1;
}
}
}
}
void getNxt(vector<int> *f) {
solve();
for (int i = n; i >= 1; --i) {
f[i] = {dp[i][0], dp[i][1]};
}
}
int Pre[N], Suf[N];
vector< array<int, 3> > mdf[N];
void add(int a, int b, int c, int d, int e) {
if (a <= b && c <= d) {
mdf[c].push_back({a, b, e}), mdf[d + 1].push_back({a, b, P - e});
}
}
int res[Q], coef[Q];
vector< pair<int, int> > qry[N];
struct Tag {
int K1, K2, B2;
Tag(int k1 = 0, int k2 = 0, int b2 = 0) {
K1 = k1, K2 = k2, B2 = b2;
}
void apply(Tag x) {
*this = Tag((K1 + x.K1) % P, (K2 + x.K2) % P, (B2 + x.B2 + 1ll * x.K2 * K1) % P);
}
} tag[1 << 17];
struct Info {
int S1, S2, S3;
Info(int s1 = 0, int s2 = 0, int s3 = 0) {
S1 = s1, S2 = s2, S3 = s3;
}
Info operator + (Info y) {
return Info((S1 + y.S1) % P, (S2 + y.S2) % P, (S3 + y.S3) % P);
}
void apply(Tag x) {
*this = Info(S1, (S2 + 1ll * S1 * x.K1) % P, (S3 + 1ll * S2 * x.K2 + 1ll * S1 * x.B2) % P);
}
} dt[1 << 17];
void pushdown(int k) {
dt[k << 1].apply(tag[k]), tag[k << 1].apply(tag[k]);
dt[k << 1 | 1].apply(tag[k]), tag[k << 1 | 1].apply(tag[k]);
tag[k] = Tag();
}
void build(int k, int l, int r) {
if (l == r) {
dt[k] = Info(Pre[l - 1], 0, 0);
return;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid), build(k << 1 | 1, mid + 1, r);
dt[k] = dt[k << 1] + dt[k << 1 | 1];
}
void modify(int k, int l, int r, int x, int y, Tag w) {
if (l > y || r < x) return;
if (l >= x && r <= y) {
dt[k].apply(w), tag[k].apply(w);
return;
}
pushdown(k);
int mid = (l + r) >> 1;
modify(k << 1, l, mid, x, y, w);
modify(k << 1 | 1, mid + 1, r, x, y, w);
dt[k] = dt[k << 1] + dt[k << 1 | 1];
}
Info query(int k, int l, int r, int x, int y) {
if (l >= x && r <= y) return dt[k];
pushdown(k);
int mid = (l + r) >> 1;
if (y <= mid) return query(k << 1, l, mid, x, y);
if (x > mid) return query(k << 1 | 1, mid + 1, r, x, y);
return query(k << 1, l, mid, x, y) + query(k << 1 | 1, mid + 1, r, x, y);
}
signed main() {
scanf("%d%d", &n, &q);
cin >> S, S = ' ' + S;
getNxt(nxt), reverse(S.begin() + 1, S.end()), getNxt(pre), reverse(S.begin() + 1, S.end()), reverse(pre + 1, pre + n + 1);
for (int i = 1; i <= n; ++i) for (auto &x : pre[i]) x = n - x + 1;
Pre[0] = Suf[n + 1] = 1;
for (int i = 1; i <= n; ++i) Pre[i] = (1 + (S[i] == '?')) * Pre[i - 1] % P;
for (int i = n; i >= 1; --i) Suf[i] = (1 + (S[i] == '?')) * Suf[i + 1] % P;
for (int i = 1; i <= n; ++i) add(i, i, i, i, (S[i] == '?' ? 2 : 1));
for (int i = 1; i < n; ++i) {
for (auto x : pre[i]) for (auto y : nxt[i + 1]) add(x, i, i + 1, y, 1);
}
for (int i = 0; (1 << i) <= n; ++i) {
for (int j = 2; j + (1 << i) <= n; ++j) {
for (int k = 0; k < 2; ++k) {
add(max(pre[j + (1 << i) - 1][k], j - 1 - (1 << i) + 1), j - 1, j + (1 << i), min(nxt[j][k ^ (i & 1)], j + (1 << i) + (1 << i) - 1), P - 1);
}
}
}
for (int i = 1, l, r; i <= q; ++i) {
scanf("%d%d", &l, &r), ++l, ++r, coef[i] = qpow(1ll * Pre[l - 1] * Suf[r + 1] % P);
qry[r].push_back({l, i});
}
build(1, 1, n);
for (int i = 1; i <= n; ++i) {
for (auto [x, y, z] : mdf[i]) {
modify(1, 1, n, x, y, Tag(z, 0, 0));
}
modify(1, 1, n, 1, n, Tag(0, Suf[i + 1], 0));
for (auto [x, y] : qry[i]) {
res[y] = query(1, 1, n, x, i).S3;
}
}
for (int i = 1; i <= q; ++i) printf("%lld\n", 1ll * res[i] * coef[i] % P);
return 0;
}
基础 01 练习题 - 刘海峰
咕。
\(\color{blue}\mathbf{Round\;2}\)
生命的循环 - 杨鑫和
使用强连通分量缩点,容易将原图转化为一张 DAG 及若干个自环,具体而言,一个点有自环当且仅当其边数不为 \(0\),其长度为所有简单环的 \(\gcd\),可以轻易计算。而对于每个 SCC,任意选一个代表点,则 DAG 上的每条边的端点由双向可达性均可以平移到其上。
此时,路径可以被拆成若干条 DAG 边和若干个自环,当不考虑自环时,路径数有限,对于这样的每条路径,其可能取到的路径的长度都位于一个等差数列上(其中首项为边权和,公差为所有自环的权值的最大公因数),故其覆盖了一个剩余类。
在 \(\mathcal{O}((n+m)B^2)\) 预处理后,所求即为若干个剩余类的并的最小循环节,类似 Traffic Blights 一题,考虑枚举 \(x\bmod(2^3\times3^2\times5\times7)\) 的值,则此时这些剩余类的模数均为质数的次幂,由于不同质数之间是独立的,所以可以在 \(\mathcal{O}(VB^2)(V=2^3\times3^2\times5\times7=2520)\) 的复杂度内简单计算,总时间复杂度 \(\mathcal{O}((n+m+V)B^2)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5005, B = 100, V = 2520, P = 1e9 + 9;
vector< pair<int, int> > G[N], rG[N];
int vis[N], scc;
stack<int> S;
void dfs1(int x) {
for (auto [v, w] : G[x]) {
if (!vis[v]++) dfs1(v);
}
S.push(x);
}
int pos[N], dep[N], g[N];
void dfs2(int x) {
for (auto [v, w] : rG[x]) {
if (!vis[v]) vis[v] = vis[x], dep[v] = dep[x] + w, dfs2(v);
}
}
vector< pair<int, int> > nG[N];
int fun[B + 5][B + 5], dp[N][B + 5][B + 5];
void dfs(int x, int y, int z) {
if (dp[x][y][z]) return;
dp[x][y][z] = 1;
for (auto [v, w] : nG[x]) dfs(v, y, (z + w % y + y) % y);
}
void Dfs(int x, int y, int z) {
dp[x][y][z] = 1;
for (auto [v, w] : nG[x]) {
int ny = fun[y][g[v]];
if (!dp[v][ny][(z + w % ny + ny) % ny]) Dfs(v, ny, (z + w % ny + ny) % ny);
}
}
template <typename T>
void simpl(T &S) {
for (int i = 1; i <= (int)S.size(); ++i) {
if (S.size() % i) continue;
int flg = 1;
for (int j = i; j < (int)S.size(); ++j) {
if (S[j] != S[j - i]) {
flg = 0;
break;
}
}
if (flg) {
S.erase(S.begin() + i, S.end());
return;
}
}
}
int cnt[V][B + 5][B + 5];
signed main() {
int n, m; scanf("%d%d%*d", &n, &m);
for (int i = 1, x, y, v; i <= m; ++i) {
scanf("%d%d%d", &x, &y, &v);
G[x].push_back({y, v}), rG[y].push_back({x, v});
}
for (int i = 1; i <= n; ++i) {
if (!vis[i]++) dfs1(i);
}
fill(vis + 1, vis + n + 1, 0);
while (!S.empty()) {
int x = S.top(); S.pop();
if (!vis[x]) vis[x] = ++scc, pos[scc] = x, dfs2(x);
}
for (int i = 1; i <= n; ++i) {
for (auto [v, w] : G[i]) {
if (vis[i] == vis[v]) g[pos[vis[i]]] = __gcd(g[pos[vis[i]]], abs(w + dep[v] - dep[i]));
else nG[pos[vis[i]]].push_back({pos[vis[v]], w + dep[v] - dep[i]});
}
}
for (int i = 0; i <= B; ++i) {
for (int j = 0; j <= B; ++j) fun[i][j] = __gcd(i, j);
}
for (int i = 1; i <= B; ++i) dfs(1, i, 0);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= B; ++j) {
for (int k = 0; k < j; ++k) dp[i][j][k] &= (j == g[i]);
}
}
int hv = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= B; ++j) {
for (int k = 0; k < j; ++k) {
if (dp[i][j][k]) Dfs(i, j, k), hv = 1;
}
}
}
vector< pair<int, int> > p;
for (int i = 1; i <= B; ++i) {
for (int j = 0; j < i; ++j) {
if (dp[pos[vis[n]]][i][j]) p.push_back({i, j});
}
}
for (auto [x, y] : p) {
int t = __gcd(x, V);
for (int i = 0; i < V; ++i) {
if (y % t == i % t) {
for (int j = 0; j < x / t; ++j) {
if ((j * V + i) % x == y) ++cnt[i][x / t][j];
}
}
}
}
vector<int> imp = {64, 81, 25, 49};
for (int i = 11; i <= B; ++i) {
int fl = 1;
for (int j = 2; j < i; ++j) fl &= (i % j != 0);
if (fl) imp.push_back(i);
}
vector< vector<int> > val(B + 5);
for (int i = 1; i <= B; ++i) val[i].resize(V * i);
for (int i = 0; i < V; ++i) {
int spe = 0;
for (int j = 1; j <= B; ++j) {
int ty = 1;
for (int k = 0; k < j; ++k) {
if (cnt[i][j][k]) {
if (j != 1) {
int tj = j * (j % 2 == 0 ? 2 : (j % 3 == 0 ? 3 : (j % 5 == 0 ? 5 : j % 7 == 0 ? 7 : j)));
if (tj > B) continue;
for (int l = k; l < tj; l += j) cnt[i][tj][l] = 1;
}
} else {
ty = 0;
}
}
if (ty) spe = 1;
}
for (auto x : imp) {
for (int j = 0; j < x; ++j) {
if (spe || cnt[i][x][j]) val[x][j * V + i] = 1;
}
}
}
map<int, int> M;
for (auto x : imp) {
simpl(val[x]);
int v = val[x].size();
for (int i = 2; i * i <= v; ++i) {
int pw = 0;
while (v % i == 0) ++pw, v /= i;
M[i] = max(M[i], pw);
}
if (v != 1) M[v] = max(M[v], 1);
}
int res = 1;
for (auto [x, y] : M) {
while (y--) res = 1ll * res * x % P;
}
printf("%d\n", res);
return 0;
}
树上简单求和 - 肖岱恩
考虑求出两棵树的括号表示,并将修改查询拆成若干条根链,那么一次修改查询就可以转化为对括号序列的前缀的若干次次修改查询。
具体而言,dfs 进入节点时,我们将节点编号加入括号序(初始为空)的末尾并赋上 \(-1\) 的系数;退出节点时,我们将节点编号加入括号序的末尾并赋上 \(1\) 的系数。
那么,我们对于前缀的修改为对于前缀的每个节点,分别将 \(a_x\) 加上当前修改的 \(v\) 乘上对应权值;查询为为对于前缀的每个节点,将答案加上当前修改的 \(v\) 乘上对应权值乘上 \(a_x\)。
这可以使用分块维护,整块和整块之间的贡献系数可以预处理,取块长 \(B=\sqrt{n}\),则时间复杂度为 \(\mathcal{O}(n+m\sqrt{n})\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5, B = 800;
int n, m;
vector<int> G[2][N];
int dep[2][N], fa[2][N], siz[2][N], son[2][N], top[2][N], vis[2][N], edp[2][N], clk, pos[2][N << 1], coef[2][N << 1];
void dfs1(int t, int x) {
siz[t][x] = 1, vis[t][x] = ++clk, pos[t][clk] = x, coef[t][clk] = 1;
for (auto v : G[t][x]) {
if (!vis[t][v]) dep[t][v] = dep[t][x] + 1, fa[t][v] = x, dfs1(t, v), siz[t][x] += siz[t][v], ((siz[t][v] > siz[t][son[t][x]]) && (son[t][x] = v));
}
edp[t][x] = ++clk, pos[t][clk] = x, coef[t][clk] = -1;
}
void dfs2(int t, int x) {
for (auto v : G[t][x]) {
if (v != fa[t][x]) top[t][v] = (v == son[t][x] ? top[t][x] : v), dfs2(t, v);
}
}
int LCA(int t, int x, int y) {
while (top[t][x] != top[t][y]) {
if (dep[t][top[t][x]] < dep[t][top[t][y]]) swap(x, y);
x = fa[t][top[t][x]];
}
return (dep[t][x] < dep[t][y] ? x : y);
}
int lst(int t, int x, int y) {
while (x) {
if (fa[t][top[t][x]] == y) return top[t][x];
x = fa[t][top[t][x]];
}
return son[t][y];
}
unsigned long long a[N], cnt[N << 1], cur[N << 1], Coef[N * 2 / B + 5][N * 2 / B + 5];
struct Sqrt0 {
int n;
unsigned long long s1[N << 1], s2[N << 1];
void modify(int x, unsigned long long v) {
s1[x] += v, s2[x / B] += v;
}
unsigned long long query(int x) {
unsigned long long S = 0;
for (int i = 0; i < x / B; ++i) S += s2[i];
for (int i = x / B * B; i <= x; ++i) S += s1[i];
return S;
}
} ds0;
struct Sqrt1 {
unsigned long long s1[N << 1], s2[N << 1];
void modify(int x, unsigned long long v) {
for (int i = x; i < (x / B + 1) * B; ++i) s1[i] += v;
for (int i = x / B + 1; i <= n * 2 / B; ++i) s2[i] += v;
}
unsigned long long query(int x) {
return s1[x] + s2[x / B];
}
} ds1;
void add(int x, int y, unsigned long long v) {
auto upd = [&](int i) {
ds0.modify(vis[1][pos[0][i]], v * coef[0][i]), ds0.modify(edp[1][pos[0][i]], -v * coef[0][i]);
};
if (y - x <= 3 * B) {
for (int i = x; i <= y; ++i) upd(i);
} else {
int l = x / B, r = y / B;
for (int i = x; i < (l + 1) * B; ++i) upd(i);
for (int i = r * B; i <= y; ++i) upd(i);
cnt[l] -= v, cnt[r - 1] += v;
x = (l + 1) * B, y = r * B - 1;
ds1.modify(x, v), ds1.modify(y + 1, -v);
}
}
unsigned long long query(int x, int y) {
unsigned long long res = ds0.query(y) - ds0.query(x - 1);
auto query = [&](int i) {
return ds1.query(vis[0][pos[1][i]]) * coef[1][i] - ds1.query(edp[0][pos[1][i]]) * coef[1][i];
};
if (y - x <= 3 * B) {
for (int i = x; i <= y; ++i) res += query(i);
} else {
int l = x / B, r = y / B;
for (int i = x; i < (l + 1) * B; ++i) res += query(i);
for (int i = r * B; i <= y; ++i) res += query(i);
for (int i = 0; i < n * 2 / B; ++i) res += cnt[i] * (Coef[i][r - 1] - Coef[i][l]);
}
return res;
}
signed main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%llu", &a[i]);
for (int t = 0; t < 2; ++t) {
for (int i = 1, x, y; i < n; ++i) {
scanf("%d%d", &x, &y);
G[t][x].push_back(y);
G[t][y].push_back(x);
}
clk = 0, dfs1(t, dep[t][1] = 1), dfs2(t, top[t][1] = 1);
}
for (int i = 1; i <= n; ++i) ds0.modify(vis[1][i], a[i]), ds0.modify(edp[1][i], -a[i]);
for (int i = 0; i < n * 2 / B; ++i) {
for (int j = 1; j <= n; ++j) cur[j] = 0;
for (int j = 1; j <= min(n * 2, (i + 1) * B - 1); ++j) cur[pos[0][j]] += coef[0][j];
unsigned long long S = 0;
for (int j = 1; j <= n * 2; ++j) {
S += cur[pos[1][j]] * coef[1][j], Coef[i][j / B] = S;
}
}
while (m--) {
int x, y; scanf("%d%d", &x, &y);
unsigned long long v; scanf("%llu", &v);
int l = LCA(0, x, y), p = lst(0, y, l);
add(vis[0][l], vis[0][x], v);
if (l != y) add(vis[0][p], vis[0][y], v);
l = LCA(1, x, y), p = lst(1, y, l);
unsigned long long res = query(vis[1][l], vis[1][x]);
if (l != y) res += query(vis[1][p], vis[1][y]);
printf("%llu\n", res);
}
return 0;
}
路径计数 - 周桓毅
咕。
\(\color{blue}\mathbf{Round\;3}\)
环上排序信息最优分割 - 仲煦北
咕。
研心 - 李静榕
咕。
无限地狱 - 陈旭磊
咕。
\(\color{blue}\mathbf{Round\;4}\)
Désive - 陈诺
咕。
分道扬镳 - 葛致远
咕。
观虫我 - 叶李蹊
咕。
\(\color{blue}\mathbf{Round\;5}\)
长野原龙势流星群 - 孙培轩
考虑二分答案 \(w\),那么一个点的 dp 值为 \(f_x=a_x-w+\sum\limits_{v\in\text{son}(x)}\max(f_v,0)\)。注意到二分答案的一步是没有用的,可以转化从大到小扫描 \(w\),那么 \(f_x\) 和 \(0\) 的大小关系只会变化 \(1\) 次。
一种理解方式是,按照当前答案从大到小枚举节点,如果合并至父节点可以使父节点答案更优就合并,由二分过程可以证明其正确性,用堆和并查集维护即可做到 \(\mathcal{O}(n\log n)\)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int p[N], fa[N], siz[N];
long long s[N];
double res[N];
int find(int x) {
return (x == fa[x] ? x : fa[x] = find(fa[x]));
}
signed main() {
int n; scanf("%d", &n);
for (int i = 2; i <= n; ++i) scanf("%d", &p[i]);
priority_queue< pair<double, int> > pq;
for (int i = 1; i <= n; ++i) scanf("%lld", &s[i]), pq.push({s[i], i}), fa[i] = i, siz[i] = 1;
while (!pq.empty()) {
int x = pq.top().second; pq.pop();
if (res[x]) continue;
res[x] = (double)s[x] / siz[x];
if (x == 1) continue;
int y = find(p[x]);
s[y] += s[x], siz[y] += siz[x], fa[x] = y;
pq.push({(double)s[y] / siz[y], y});
}
for (int i = 1; i <= n; ++i) printf("%.10lf\n", res[i]);
return 0;
}
Classical Counting Problem - 翟鹏昊
咕。
运筹帷幄 - 赵海鲲
咕。

浙公网安备 33010602011771号