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\) 就是这个连通块最高的隔板高度。

然后合并两个连通块的时候和最终求答案类似:

\[ans' = (ans_x + (w - mx_x)) \times (ans_y + (w - mx_y)) \]

然后更新 \(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];

放一份 std。
#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;
}
    
posted @ 2025-08-13 17:04  Dtwww  阅读(7)  评论(0)    收藏  举报