NOIP2024 题解
考场上一直都不知道在想什么,心态也很不好,结果 B 一直不会,最后会了 C 还没写完。
感觉这个赛季对我来说就已经结束了吧 /hsh /wn
本来是想退役的,但是学文化课对我来说太痛苦了,而且我还是比较热爱 OI 的,所以就再试着走一走吧。
P11361 [NOIP2024] 编辑字符串
发现限制就是将 \(s\) 和 \(t\) 划分成了若干段区间,每段可以任意交换,求 LCS 的最大值。
发现直接贪心匹配肯定是对的,因为你与其让它在后面匹配上不如让它直接在前面匹配上。
模拟这个过程即可,时间复杂度线性。
constexpr int N = 1e5 + 5;
int n;
char s1[N], s2[N], t1[N], t2[N];
void slv() {
Read(n);
Read(s1 + 1), Read(s2 + 1);
Read(t1 + 1), Read(t2 + 1);
vector<tuple<int, int, int, int>> A, B;
for (int l = 1, r; l <= n; l = r + 1) {
r = l;
if (t1[l] == '0') {
if (s1[l] == '0') A.emplace_back(l, l, 1, 0);
else A.emplace_back(l, l, 0, 1);
continue;
}
int c0 = 0, c1 = 0;
while (r + 1 <= n && t1[r + 1] != '0') r ++;
for (int i = l; i <= r; i ++) c0 += s1[i] == '0', c1 += s1[i] == '1';
A.emplace_back(l, r, c0, c1);
}
for (int l = 1, r; l <= n; l = r + 1) {
r = l;
if (t2[l] == '0') {
if (s2[l] == '0') B.emplace_back(l, l, 1, 0);
else B.emplace_back(l, l, 0, 1);
continue;
}
int c0 = 0, c1 = 0;
while (r + 1 <= n && t2[r + 1] != '0') r ++;
for (int i = l; i <= r; i ++) c0 += s2[i] == '0', c1 += s2[i] == '1';
B.emplace_back(l, r, c0, c1);
}
int p = 0, q = 0; int ans = 0;
while (p < A.size() && q < B.size()) {
int al, ar, ac0, ac1; tie(al, ar, ac0, ac1) = A[p];
int bl, br, bc0, bc1; tie(bl, br, bc0, bc1) = B[q];
int c0 = min(ac0, bc0), c1 = min(ac1, bc1);
ac0 -= c0, bc0 -= c0, ac1 -= c1, bc1 -= c1, ans += c0 + c1;
int len = min(ar, br) - al + 1;
len -= c0 + c1;
if (len) {
if (ar > br) {
if (bc0) ac1 -= len;
if (bc1) ac0 -= len;
}
if (ar < br) {
if (ac0) bc1 -= len;
if (ac1) bc0 -= len;
}
}
if (ar == br) p ++, q ++;
else if (ar > br) q ++, A[p] = make_tuple(br + 1, ar, ac0, ac1);
else p ++, B[q] = make_tuple(ar + 1, br, bc0, bc1);
}
Write(ans, '\n');
return;
}
P11362 [NOIP2024] 遗失的赋值
发现每段是独立的,每段的答案可以用总方案数减掉不合法的得出,最后乘起来即可。
时间复杂度 \(O(m \log n)\),当然也可以做到 \(O(m + \sqrt n)\)。
constexpr int N = 1e5 + 5;
int n, m;
mint v;
void slv() {
Read(n, m), v = Read<int>();
vector<pair<int, int>> lim;
for (int i = 1; i <= m; i ++) {
int c, d; Read(c, d);
lim.emplace_back(c, d);
}
sort(lim.begin(), lim.end());
lim.erase(unique(lim.begin(), lim.end()), lim.end());
m = (int)lim.size();
for (int i = 1; i < m; i ++)
if (lim[i].fir == lim[i - 1].fir)
{ Puts("0"); return; }
mint ans = 1;
ans *= v.Pow(2 * (lim.front().fir - 1));
ans *= v.Pow(2 * (n - lim.back().fir));
for (int i = 1; i < m; i ++) {
int l = lim[i - 1].fir, r = lim[i].fir;
ans *= v.Pow(2 * (r - l)) - v.Pow(r - l - 1) * (v - 1);
}
Write((int)ans, '\n');
return;
}
P11363 [NOIP2024] 树的遍历
先考虑 \(k = 1\) 怎么做,发现每个点的连着的边一定是连成一个链,然后你钦定根就是给每条链钦定了一个入边,所以答案是 \(\prod (deg_u - 1)!\)。
不难想到容斥,现在要计算的是钦定一个集合的边为根时的方案数。
根据 \(k = 1\) 的结论,发现只有在钦定的边在一条链上的时候方案数不为 0,否则一条链会钦定三个入边。
发现如果钦定在一条链上,只有钦定了 \(\le 2\) 条边的时候有贡献,因为:
钦定一条边是的方案数是 \(\prod(deg_u - 1)!\);钦定两条边的时候对于这两条边之间的点,它们相连的边最后一定是钦定了一条入边一条出边,所以它们的贡献是 \((deg_u - 2)!\),奇遇点的贡献还是 \((deg_u - 1)!\),所以直接 DP 即可。
时间复杂度 \(O(n)\)。
constexpr int N = 1e5 + 5;
int n, k, deg[N];
vector<pair<int, int>> G[N];
mint val[N], f[N], g[N];
bool mrk[N];
void DP(int u, int fa) {
f[u] = g[u] = 0;
for (auto [v, id] : G[u]) {
if (v == fa) continue; DP(v, u);
if (mrk[id]) g[u] += f[v], f[v] = 1;
g[u] += g[v] + f[u] * f[v] * val[u], f[u] += f[v];
}
f[u] *= val[u]; return;
}
void slv() {
Read(n, k);
for (int i = 1; i < n; i ++) {
int u, v; Read(u, v);
G[u].emplace_back(v, i);
G[v].emplace_back(u, i);
++ deg[u], ++ deg[v], mrk[i] = false;
}
for (int i = 1; i <= k; i ++)
mrk[Read<int>()] = true;
mint ans = 1;
for (int u = 1; u <= n; u ++)
ans *= comb.fac(deg[u] - 1), val[u] = comb.inv(deg[u] - 1);
DP(1, 0);
Write((int)(ans * (k - g[1])), '\n');
return;
}
void clr() {
for (int u = 1; u <= n; u ++) G[u].clear(), deg[u] = 0;
return;
}
P11364 [NOIP2024] 树上查询
首先有个结论:
证明就是考虑一定有相邻的两个位置在 \(\operatorname{LCA*}(l, r)\) 的不同子树中,否则 \(\operatorname{LCA*}(l, r)\) 就是错的。
所以我们把 \(k = 1\) 干掉之后就是查询序列上所有长度 \(\ge k - 1\) 的子区间最小值最大是多少。
暴力就是枚举每个长度恰好为 \(k - 1\) 的子区间,查询是所有包含它的区间的区间最小值的最大值。
枚举区间最小值,求出极长区间,不难发现只有 \(O(n)\) 个。
放到二维平面上,就是 \(O(n)\) 个点,\(O(n^2)\) 次左上角矩形最大值查询。
然后发现对于一组 \((l, r, k)\),查询的就是 \((l, l + k - 2) \sim (r - k + 1, r - 1)\) 这条直线的左上角的最大值。
可以这样拆一下:
![[Pasted image 20241216175033.png]]
原本的查询是灰线内的部分,现在变成了三个矩形,两边扫描线即可。
时间复杂度 \(O(n \log n)\)。
constexpr int N = 5e5 + 5, LG = 19;
int n, q, dep[N], dfn[N], val[N], ans[N];
pair<int, int> st[LG][N], rng[N];
vector<int> G[N];
struct Segment_Tree {
int mx[N << 1], pos[N], fa[N << 1];
#define ls(p) (L + R)
#define rs(p) (ls(p) ^ 1)
void Build(int p = 1, int L = 1, int R = n) {
mx[p] = 0;
if (L == R) { pos[L] = p; return; }
int Mid = L + R >> 1;
Build(ls(p), L, Mid);
Build(rs(p), Mid + 1, R);
fa[ls(p)] = p, fa[rs(p)] = p;
return;
}
void Update(int p, int k) {
p = pos[p];
while (p) cmax(mx[p], k), p = fa[p];
return;
}
int Query(int l, int r, int p = 1, int L = 1, int R = n) {
if (l <= L && R <= r) return mx[p];
int Mid = L + R >> 1;
if (r <= Mid) return Query(l, r, ls(p), L, Mid);
if (Mid < l) return Query(l, r, rs(p), Mid + 1, R);
return max(Query(l, r, ls(p), L, Mid), Query(l, r, rs(p), Mid + 1, R));
}
#undef ls
#undef rs
} tr;
struct Counter {
vector<tuple<int, int, int>> upd;
vector<tuple<int, int, int, int>> qry;
void Add_Point(int x, int y, int val)
{ upd.emplace_back(x, y, val); return; }
void Add_Query(int x, int l, int r, int id)
{ qry.emplace_back(x, l, r, id); return; }
vector<int> Solve() {
vector<int> ans(q, 0); tr.Build();
sort(upd.begin(), upd.end());
sort(qry.begin(), qry.end());
for (int i = 0, j = 0; i < (int)qry.size(); i ++) {
auto [x, l, r, id] = qry[i];
while (j < (int)upd.size() && get<0>(upd[j]) <= x)
tr.Update(get<1>(upd[j]), get<2>(upd[j])), ++ j;
cmax(ans[id], tr.Query(l, r));
}
return ans;
}
};
void DFS(int u, int fa) {
static int dfc = 0; dfn[u] = ++ dfc;
st[0][dfn[u]] = {dep[u] = dep[fa] + 1, fa};
for (auto v : G[u]) if (v ^ fa) DFS(v, u);
return;
}
pii Query(int l, int r) {
int k = __lg(r - l + 1);
return min(st[k][l], st[k][r - (1 << k) + 1]);
}
int Get_Lca(int u, int v) {
if (u == v) return u;
if ((u = dfn[u]) > (v = dfn[v])) swap(u, v);
return Query(u + 1, v).sec;
}
void slv() {
Read(n);
for (int i = 1; i < n; i ++) {
int u, v; Read(u, v);
G[u].emplace_back(v), G[v].emplace_back(u);
}
DFS(1, 0), Read(q);
for (int i = 1; i <= __lg(n); i ++)
for (int j = 1; j + (1 << i) - 1 <= n; j ++)
st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
for (int u = 1; u < n; u ++) val[u] = dep[Get_Lca(u, u + 1)];
val[n] = 0; vector<int> stk; stk.emplace_back(0);
for (int u = 1; u <= n; u ++) {
while (stk.size() > 1 && val[u] < val[stk.back()])
rng[stk.back()].sec = u - 1, stk.pop_back();
rng[u].fir = stk.back() + 1, stk.emplace_back(u);
}
for (int u = 1; u <= n; u ++) st[0][u] = {dep[u], u};
for (int i = 1; i <= __lg(n); i ++)
for (int j = 1; j + (1 << i) - 1 <= n; j ++)
st[i][j] = max(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
Counter c1, c2;
for (int u = 1; u < n; u ++)
c1.Add_Point(rng[u].fir, rng[u].sec, val[u]),
c2.Add_Point(rng[u].fir - rng[u].sec, rng[u].fir, val[u]);
for (int i = 0; i < q; i ++) {
int l, r, k; Read(l, r, k);
if (k == 1) {
k = __lg(r - l + 1);
ans[i] = max(st[k][l], st[k][r - (1 << k) + 1]).fir;
} else {
c1.Add_Query(l, l + k - 2, n, i);
c1.Add_Query(r - k + 1, r - 1, n, i);
c2.Add_Query(2 - k, l, r + 1 - k, i);
}
}
auto res1 = c1.Solve(), res2 = c2.Solve();
for (int i = 0; i < q; i ++)
Write(max({ans[i], res1[i], res2[i]}), '\n');
return;
}