2025 暑期 mx 集训 7.25
T1
https://www.mxoj.net/problem/P110058?contestId=67
题意
\(n\) 个点,每个点高度为 \(a_i \in [1, m]\)。
你要找到一个 \(x\),使得 \(a_i = a_{i - 1} + x \ \forall i \in (1,n]\)。
你可以修改一些 \(a_i\) 使他变成 \([1, m]\) 中的任意一个数。求最小操作次数。
\(n,m\leq 10^6\)。
Solution
考虑这个 \(x\) 之有 \(O(\frac{m}{n})\) 种,所以我们可以考虑如何 \(O(n)\) 算在 \(x\) 确定时的答案。
容易发现 \(a_i = (i - 1)x + h\)。所以我们只需要统计相同的 \(h\) 最多有几个即可。用数组实现。STL 常数有点大。
时间复杂度 \(O(n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, inf = 0x3f3f3f3f;
int n, m, a[N], b[N];
int main()
{
cin.tie(0)->ios::sync_with_stdio(false);
int _; cin >> _;
while (_--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
int ans = n;
for (int i = 0; i <= m / max(n - 1, 1) + 5; i++) {
int c = 0;
for (int j = 1; j <= n; j++) {
if (a[j] >= (j - 1) * i + 1 && a[j] + (n - j) * i <= m) {
b[a[j] - (j - 1) * i]++;
c = max(c, b[a[j] - (j - 1) * i]);
}
}
for (int j = 1; j <= n; j++) {
if (a[j] >= (j - 1) * i && a[j] + (n - j) * i <= m) {
b[a[j] - (j - 1) * i] = 0;
}
}
ans = min(ans, n - c);
}
cout << ans << "\n";
}
return 0;
}
T2
https://www.mxoj.net/problem/P110059?contestId=67
题意
给你一张无向图,每条边上有一个隔板高 \(h_i\)。
每个点有一些雨水,当某个点雨水高度大于和他相连的边的隔板高度时,水会从多的地方流向少的地方。
设每个点最终的雨水量为 \(a_i\),那么 \(a_i\) 是 \([1,H]\) 之间的整数。求有多少种 \(a\) 序列。对 \(998244353\) 取模。
\(n,m\leq 2\times 10^5,H\leq 10^9\)。
Solution
首先考虑如果一个隔板非常高,那么他可能是无用的。因为太高了,根本够不到他。
然后考虑如果水漫过了一个隔板,那么这两个点的水位只可能相等了。
也就是可以把他们看作一个点。所以这就很 Kruskal。
设最高的有用的隔板高为 \(p\),那么答案肯定是由 \(< p\) 的部分 \(+ (p, H]\) 的部分拼成。
后面这部分是好算的,就是 \(H - p\)。前面这块考虑跑最小生成树的同时求出。
考虑设 \(ans_i\) 为第 \(i\) 个连通块 \(< mx_i\) 的答案,\(mx_i\) 就是这个连通块最高的隔板高度。
然后合并两个连通块的时候和最终求答案类似:
然后更新 \(mx\) 即可。
时间复杂度 \(O(n\log n)\)。
Code
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5 + 10, P = 998244353;
int n, m, H, p[N];
ll mx[N], ans[N];
struct node { int u, v, w; } e[N];
int fifa(int x) { return p[x] == x ? x : p[x] = fifa(p[x]); }
void merge(int x, int y, int w)
{
x = fifa(x), y = fifa(y);
if (x == y) return;
p[x] = y;
ans[y] = ((ans[x] + (w - mx[x])) % P * ((ans[y] + (w - mx[y]))) % P) % P;
mx[y] = w;
}
int main()
{
cin.tie(0)->ios::sync_with_stdio(false);
cin >> n >> m >> H;
for (int i = 1; i <= m; i++) {
int u, v, w; cin >> u >> v >> w;
e[i] = { u, v, w };
}
for (int i = 1; i <= n; i++) ans[i] = 0, mx[i] = -1, p[i] = i;
sort (e + 1, e + m + 1, [&](node x, node y) { return x.w < y.w; });
for (int i = 1; i <= m; i++) {
int u = e[i].u, v = e[i].v, w = e[i].w;
merge(u, v, w);
}
cout << (ans[fifa(1)] + (H - mx[fifa(1)])) % P;
return 0;
}
T3
https://www.mxoj.net/problem/P110060?contestId=67
题意
一共 \(2n\) 个点,每行 \(n\) 个共 \(2\) 行。每个点有颜色。
\((i, j)\) 表示左脚在 \(i\) 右脚在 \(j\) 上。
你一开始在 \((0, 0)\) 要走到 \((n + 1, n + 1)\)。你要保证两只脚所处的点的颜色相等。
你可以从 \((i,j)\) 选择 \((k,l),i<k,j<l\)。然后去到 \((k,l)\) 并花费 \((k - i)^2 + (l - j)^2\) 的代价。
求最小花费。
\(n\leq 3\times 10^3\)。
Solution
\(O(n^4)\) 暴力转移随便做。
然后考虑钦定每次只移动一个,可以先移动一个,然后移动另一个。
设 \(f_{i,j,0/1}\) 表示在 \((i,j)\),现在该动哪只脚了。
转移就枚举这只脚去哪即可。\(O(n^3)\)。
然后转移是这样的:
\(f_{i,j,0} = \min f_{i,k,1} + (j - k)^2\)
\(f_{i,j,1} = \min f_{k,j,0} + (i-k)^2 \ (col_{i,0} = col_{j,1})\)
直接斜率优化转移即可。(但我不会)
Code
暴力
memset(f, 0x3f, sizeof f);
f[0][0][1] = 0;
for (int i = 0; i <= n + 1; ++i) {
for (int j = 0; j <= n + 1; ++j) {
// 计算 f[i][j][0]:最后动右脚,前序状态是右脚在k(k < j)
// 中间状态,无需颜色相等
for (int k = 0; k < j; ++k)
f[i][j][0] = min(f[i][j][0], f[i][k][1] + (j - k) * (j - k));
// 计算 f[i][j][1]:最后动左脚,前序状态是左脚在k(k < i)
// 需满足颜色匹配条件a[i] == b[j]
if (a[i] == b[j])
for (int k = 0; k < i; ++k)
f[i][j][1] = min(f[i][j][1], f[k][j][0] + (i - k) * (i - k));
}
}
cout << f[n + 1][n + 1][1];
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
int n;
int f[N][N][2];
int a[N], b[N];
int q1[N], q2[N][N], l2[N], r2[N];
int main() {
freopen("stone.in", "r", stdin);
freopen("stone.out", "w", stdout);
scanf("%d", &n);
for (int i = 0; i <= n + 1; i++) {
scanf("%d", &a[i]);
}
for (int i = 0; i <= n + 1; i++) {
scanf("%d", &b[i]);
}
memset(f, 10, sizeof(f));
f[0][0][1] = 0;
for (int i = 0; i <= n + 1; i++) {
l2[i] = 1;
}
for (int i = 0; i <= n + 1; i++) {
int l1 = 1, r1 = 0;
for (int j = 0; j <= n + 1; j++) {
// 优化f[i][j][0]的转移
while (l1 < r1 &&
f[i][q1[l1 + 1]][1] + q1[l1 + 1] * q1[l1 + 1] - f[i][q1[l1]][1] - q1[l1] * q1[l1] <=
2 * j * (q1[l1 + 1] - q1[l1])) {
l1++;
}
// 优化f[i][j][1]的转移
while (l2[j] < r2[j] &&
f[q2[j][l2[j] + 1]][j][0] + q2[j][l2[j] + 1] * q2[j][l2[j] + 1] - f[q2[j][l2[j]]][j][0] - q2[j][l2[j]] * q2[j][l2[j]] <=
2 * i * (q2[j][l2[j] + 1] - q2[j][l2[j]])) {
l2[j]++;
}
// 更新f[i][j][0]
if (l1 <= r1) {
f[i][j][0] = f[i][q1[l1]][1] + (j - q1[l1]) * (j - q1[l1]);
}
// 更新f[i][j][1]
if (l2[j] <= r2[j] && a[i] == b[j]) {
f[i][j][1] = f[q2[j][l2[j]]][j][0] + (i - q2[j][l2[j]]) * (i - q2[j][l2[j]]);
}
// 维护q2[j]队列
if (f[i][j][0] < f[0][0][0]) {
while (l2[j] < r2[j] &&
(f[i][j][0] + i * i - f[q2[j][r2[j] - 1]][j][0] - q2[j][r2[j] - 1] * q2[j][r2[j] - 1]) * (q2[j][r2[j]] - q2[j][r2[j] - 1]) <=
(f[q2[j][r2[j]]][j][0] + q2[j][r2[j]] * q2[j][r2[j]] - f[q2[j][r2[j] - 1]][j][0] - q2[j][r2[j] - 1] * q2[j][r2[j] - 1]) * (i - q2[j][r2[j] - 1])) {
r2[j]--;
}
q2[j][++r2[j]] = i;
}
// 维护q1队列
if (f[i][j][1] < f[0][0][0]) {
while (l1 < r1 &&
(f[i][j][1] + j * j - f[i][q1[r1 - 1]][1] - q1[r1 - 1] * q1[r1 - 1]) * (q1[r1] - q1[r1 - 1]) <=
(f[i][q1[r1]][1] + q1[r1] * q1[r1] - f[i][q1[r1 - 1]][1] - q1[r1 - 1] * q1[r1 - 1]) * (j - q1[r1 - 1])) {
r1--;
}
q1[++r1] = j;
}
}
}
printf("%d", f[n + 1][n + 1][1]);
return 0;
}
T4
https://www.mxoj.net/problem/P110061?contestId=67
题意
有 \(n\) 个点,初始时第 \(i\) 个点为 \(i\)。
有 \(m\) 种操作形如:\(x,y\) 表示把颜色为 \(x\) 的点的颜色变为 \(y\)。
然后有 \(q\) 次查询,每次查询进行 \([l,r]\) 内的操作最终有多少极长连续段。
\(n,m,q \leq 5\times 10^5\)。
Solution
首先考虑固定 \(l\) 移动 \(r\)。
我们可以建图,直接连 \(x\to y\) 的边。但是如果有 \(1\to 2, 2 \to 1\) 就毁了。
考虑每次建立虚点 \(p\),\(p \to x, p\to y\),然后 \(p\) 的颜色和 \(y\) 一样。
这样当 \(r\) 移到最后的时候就建出来了一个森林。
考虑颜色段数量只需要知道相邻两个位置上的颜色是否一样。
对于 \(i, i + 1\) 他们首次变得一样的时刻是 \(lca\)。
用数据结构维护区间加减即可。
接下来考虑移动 \(l\),这个我不会。
考虑在前面新建一个操作有什么影响。
其实只需要新建一个节点 \(p\) 作为 \(x,y\) 的父亲,然后 \(y\) 原本的父亲作为 \(p\) 的父
亲即可,可以发现只有 \(x\) 的祖先关系会更改,重构 \(x - 1, x, x + 1\) 之间的
贡献即可。
综上我们需要加叶子求 LCA 和树状数组,前者可以用倍增动态维护。
后面这部分还不会,先放个 std 吧。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 7;
inline int read() {
int X = 0;
bool flag = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') flag = 0;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
X = (X << 1) + (X << 3) + ch - '0';
ch = getchar();
}
if (flag) return X;
return ~(X - 1);
}
struct node {
int id, l, r;
} q[N];
bool cmp(node a, node b) {
return a.l > b.l;
}
int rot[N], dep[N];
int t, f[N][30];
int LCA(int x, int y) {
if (rot[x] != rot[y]) return 0;
if (dep[x] < dep[y]) swap(x, y);
for (int i = t; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) {
x = f[x][i];
}
}
if (x == y) return x;
for (int i = t; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
int n, m, Q;
int x[N], y[N];
int tree[N];
int lowbit(int x) {
return x & (-x);
}
void add(int x, int v) {
for (int i = x; i <= m; i += lowbit(i)) {
tree[i] += v;
}
}
int ask(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) {
res = res + tree[i];
}
return res;
}
void Extand(int i) {
int p = i + n;
dep[p] = dep[y[i]];
dep[y[i]] = dep[x[i]] = dep[p] + 1;
if (!f[y[i]][0]) {
f[p][0] = p;
} else {
f[p][0] = f[y[i]][0];
}
f[y[i]][0] = f[x[i]][0] = p;
for (int k = 1; k <= t; k++) {
f[p][k] = f[f[p][k - 1]][k - 1];
}
for (int k = 1; k <= t; k++) {
f[x[i]][k] = f[f[x[i]][k - 1]][k - 1];
}
for (int k = 1; k <= t; k++) {
f[y[i]][k] = f[f[y[i]][k - 1]][k - 1];
}
rot[x[i]] = rot[y[i]];
}
int ans[N];
int main() {
n = read();
m = read();
Q = read();
t = log2(m);
for (int i = 1; i <= m; i++) {
x[i] = read();
y[i] = read();
}
for (int i = 1; i <= Q; i++) {
q[i].id = i;
q[i].l = read();
q[i].r = read();
}
sort(q + 1, q + Q + 1, cmp);
for (int i = 1; i <= n; i++) {
rot[i] = i;
}
int k = m + 1;
add(1, n);
for (int i = 1; i <= Q; i++) {
while (k > q[i].l) {
k--;
if (x[k] == y[k]) continue;
if (x[k] != 1) {
int u = LCA(x[k], x[k] - 1);
if (u) add(u - n, 1);
}
if (x[k] != n) {
int u = LCA(x[k], x[k] + 1);
if (u) add(u - n, 1);
}
Extand(k);
if (x[k] != 1) {
int u = LCA(x[k], x[k] - 1);
if (u) add(u - n, -1);
}
if (x[k] != n) {
int u = LCA(x[k], x[k] + 1);
if (u) add(u - n, -1);
}
}
ans[q[i].id] = ask(q[i].r);
}
for (int i = 1; i <= Q; i++) {
printf("%d\n", ans[i]);
}
return 0;
}

浙公网安备 33010602011771号