2025 暑期 mx 集训 7.19

T1

https://www.mxoj.net/problem/P110016?contestId=49

题意

给你一个 \(n \times n\) 的网格,你每一步可以选择下或者右,从 \((1,1)\) 走到 \((n,n)\),走过的路径把整个网格划分成 \(A,B\) 两部分。

\(\min( \max_{x\in A, y\in B} \left | x - y\right |)\)

\(n\leq 500\)

Solution

考虑整个网格的最大值和最小值一定都选在同一个里了。

不妨设为 \(A\),那剩下的是 \(B\),此时是求 \(\min(\max_{y\in B}( \left | y - mx\right |, \left | y - mn\right |))\)

然后考虑这个 \(B\) 里一定包含角上的元素,然后其余元素全删掉,因为这样起码不会更劣,反而可能更优。

所以就是 \(A\)\((1, n)\) 求一下,\(B\)\((n, 1)\) 求一下,两者取 \(\min\) 即可。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 5e2 + 10, inf = 0x3f3f3f3f;

int n, a[N][N];

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) cin >> a[i][j];
    int mn1 = inf, mx1 = 0, mn2 = inf, mx2 = 0;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) {
        if (i != 1 || j != n) mn1 = min(mn1, a[i][j]), mx1 = max(mx1, a[i][j]);
        if (i != n || j != 1) mn2 = min(mn2, a[i][j]), mx2 = max(mx2, a[i][j]);
    }
    cout << min(max(abs(a[1][n] - mn1), abs(a[1][n] - mx1)), max(abs(a[n][1] - mn2), abs(a[n][1] - mx2)));
    return 0;
}

T2

https://www.mxoj.net/problem/P110017?contestId=49

题意

给你一个 \(n\times n\) 的网格,有些格子能涂色,有些不能涂色。

你需要求出一个最大的 \(l\),使得可以找到两个 \(1\times l\)\(l \times 1\) 的连续格子可以被涂色。他们不能相交。

\(n\leq 1500\)

Solution

首先考虑枚举其中一个,然后预处理另一个。

我们可以预处理:前 \(i\) 行最大的 \(l\),后 \(i\) 行最大的 \(l\),前 \(i\) 列最大的 \(l\),后 \(i\) 列最大的 \(l\)

这些可以 \(n^2\) 预处理出来。

然后可以二分答案直接判断即可。具体可看代码。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 1.5e3 + 10, inf = 0x3f3f3f3f;

int n, a[N][N], s[N][N], l[N][2], r[N][2];

void clear() { for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) s[i][j] = 0; }

bool chk(int x)
{
    for (int i = 1; i <= n; i++) {
        int c = 0;
        for (int j = 1, k = 1; j + x - 1 <= n; j++) {
            while (k <= j + x - 1) {
                c += !a[i][k];
                k++;
            }
            // (i, j) -> (i, j + x - 1)
            if (!c) {
                if (l[i - 1][0] >= x || l[i + 1][1] >= x || r[j - 1][0] >= x || r[j + x][1] >= x) return 1;
            }
            c -= !a[i][j];
        }
    }
    for (int j = 1; j <= n; j++) {
        int c = 0;
        for (int i = 1, k = 1; i + x - 1 <= n; i++) {
            while (k <= i + x - 1) {
                c += !a[k][j];
                k++;
            }
            // (i, j) -> (i + x - 1, j)
            if (!c) {
                if (l[i - 1][0] >= x || l[i + x][1] >= x || r[j - 1][0] >= x || r[j + 1][1] >= x) return 1;
            }
            c -= !a[i][j];
        }
    }
    return 0;
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) {
        char c; cin >> c;
        a[i][j] = (c == '.');
    }
    clear();
    for (int i = 1; i <= n; i++) {
        int c = 0;
        l[i][0] = l[i - 1][0];
        for (int j = 1; j <= n; j++) {
            if (a[i][j]) {
                c++;
                s[i][j] = s[i - 1][j] + 1;
                l[i][0] = max(l[i][0], s[i][j]);
            } else l[i][0] = max(l[i][0], c), c = 0;
        }
        l[i][0] = max(l[i][0], c);
    }
    clear();
    for (int i = n; i >= 1; i--) {
        int c = 0;
        l[i][1] = l[i + 1][1];
        for (int j = 1; j <= n; j++) {
            if (a[i][j]) {
                c++;
                s[i][j] = s[i + 1][j] + 1;
                l[i][1] = max(l[i][1], s[i][j]);
            } else l[i][1] = max(l[i][1], c), c = 0;
        }
        l[i][1] = max(l[i][1], c);
    }
    clear();
    for (int j = 1; j <= n; j++) {
        int c = 0;
        r[j][0] = r[j - 1][0];
        for (int i = 1; i <= n; i++) {
            if (a[i][j]) {
                c++;
                s[i][j] = s[i][j - 1] + 1;
                r[j][0] = max(r[j][0], s[i][j]);
            } else r[j][0] = max(r[j][0], c), c = 0;
        }
        r[j][0] = max(r[j][0], c);
    }
    clear();
    for (int j = n; j >= 1; j--) {
        int c = 0;
        r[j][1] = r[j + 1][1];
        for (int i = 1; i <= n; i++) {
            if (a[i][j]) {
                c++;
                s[i][j] = s[i][j + 1] + 1;
                r[j][1] = max(r[j][1], s[i][j]);
            } else r[j][1] = max(r[j][1], c), c = 0;
        }
        r[j][1] = max(r[j][1], c);
    }
    int l = 0, r = n, res = 0;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (chk(mid)) l = (res = mid) + 1;
        else r = mid - 1;
    }
    cout << res << "\n";
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    int _; cin >> _;
    while (_--) solve();
    return 0;
}

T3

https://www.mxoj.net/problem/P110018?contestId=49

题意

给你 \(m\)\(1\sim n\) 的排列。这是他们的排名,在第 \(i\) 个排列中,\(p_{i,j}\) 的排名为 \(j\)

\(q\) 次询问,每次给你 \(x,y\)

你要找到一个长为 \(l + 1\) 的序列 \(b\) 满足:

  • \(b_1 = x, b_{l + 1} = y\)
  • \(\forall i \in [1, l] \ b_i < b_{i + 1}\)

你要找到最小的 \(l\) 使得满足这个东西。每次询问输出 \(l\)

\(n,q \leq 10^5, m\leq 5\)

Solution

首先考虑你往后找,肯定找一个能在这 \(m\) 个序列中到达位置最靠前的那一个。

然后我们就有 \(O(nmq)\) 的做法。

接着考虑优化。这个东西往后跳,然后具有可合并性,考虑倍增。

\(f_{i,j,k}\) 表示从 \(i\) 开始跳,跳 \(2^j\) 步,最终跳到第 \(k\) 个序列上,所能到达最靠前的位置。

然后初始化就是 \(f_{i,0,j} = \min \{ pos_{j,i} \}\)

其中 \(pos_{j,i}\) 表示第 \(j\) 个序列中 \(i\) 的位置。

由于你肯定只能往后跳,所以肯定是从后往前扫,维护 \(pos\)

然后考虑预处理倍增数组。

还是枚举 \(i,j,k\),然后考虑上一步从哪个序列跳过来,于是枚举上一个序列 \(l\)

\[f_{i,j,k} = \min \{ f_{a_{l,f_{i,j-1,l}}, j - 1, k} \} \]

就是说你从 \(i\)\(2^{j-1}\)\(l\) 上,然后从 \(l\) 在跳过来。其中 \(a_{i,j}\) 表示第 \(i\) 个序列上排名为 \(j\) 的数。

然后考虑查询。

首先如果能直接跳过去,那就不用倍增了。

接着考虑套路的枚举跳 \(2^i\) 步,然后用一个数组 \(mn_j\) 维护在 \(j\) 这个序列上能到达最靠前的位置。

然后我们新开一个辅助数组 \(mn2_j\) 维护从 \(mn_j\) 跳到了哪里。

然后如果 \(mn2_j < pos_{j, y}\) 此时说明一步就可以跳过去了。但是此时我们并不能跳过去。

你可能会问,那我直接输出答案 \(+1\) 不就行。

然而,如果你一步直接跳过了呢?就是此时不需要 \(+1\)。所以这就没法直接判了。

然后考虑我们输出 \(ans + 2\)

那么距离答案 \(1\) 步是很好判的。因为每次我们都是第一次到这个位置,只要有一个跳一步就可以跳过去我们就直接 continue 掉。

于是最终输出 \(ans + 2\) 即可。

tips:
有一种不用 \(ans + 2\) 的查询,但是多个 \(\log\)
考虑答案有单调性,于是二分答案 \(mid\)。然后我们跳恰好 \(mid\) 次。
此时我们用 \(mn_i\)\(pos_{i,y}\) 去比较,如果有一个比他小,则这个 \(mid\) 合法。\(r\) 往左移,否则 \(l\) 右移。
然后最终输出 \(ans + 1\) 即可。

Code

另一个查询的写法

int l = 1, r = n, res = -2;
while (l <= r) {
    int mid = (l + r) / 2;
    for (int i = 1; i <= m; i++) mn[i] = p[i][x];
    for (int i = 17; i >= 0; i--) if (mid & (1 << i)) {
        mem(mn2);
        for (int j = 1; j <= m; j++)
            for (int k = 1; k <= m; k++)
                getmn(mn2[j], z[a[k][mn[k]]][i][j]);
        for (int j = 1; j <= m; j++) mn[j] = mn2[j];
    }
    bool f = 0;
    for (int j = 1; j <= m; j++)
        if (mn[j] <= p[j][y]) {
            f = 1;
            break;
        }
    if (f) r = (res = mid) - 1;
    else l = mid + 1;
}
cout << res + 1 << "\n";

#include <bits/stdc++.h>

using namespace std;

#define mem(a) memset(a, 0x3f, sizeof a)

const int N = 1e5 + 10, M = 6;

int n, m, q;
int a[M][N], z[N][18][M], mn[M], mn2[M], p[M][N];

void getmn(int &a, int b) { a = min(a, b); }

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) cin >> a[i][j], p[i][a[i][j]] = j;
    mem(z);
    for (int i = 1; i <= m; i++) {
        mem(mn);
        for (int j = n; j >= 1; j--) {
            for (int k = 1; k <= m; k++) {
                mn[k] = min(mn[k], p[k][a[i][j]]);
                getmn(z[a[i][j]][0][k], mn[k]);
            }
        }
    }
    for (int k = 1; k <= 17; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                for (int l = 1; l <= m; l++) 
                    getmn(z[i][k][j], z[a[l][z[i][k - 1][l]]][k - 1][j]);
    cin >> q;
    while (q--) {
        int x, y; cin >> x >> y;
        int ans = 0; bool ok = 0;
        for (int i = 1; i <= m; i++) {
            mn[i] = p[i][x];
            if (p[i][x] < p[i][y]) {
                cout << "1\n";
                ok = 1;
                break;
            }
        }
        if (ok) continue;
        for (int i = 17; i >= 0; i--) {
            mem(mn2);
            for (int j = 1; j <= m; j++)
                for (int k = 1; k <= m; k++) 
                    getmn(mn2[j], z[a[k][mn[k]]][i][j]);
            bool f = 0;
            for (int j = 1; j <= m; j++)
                if (mn2[j] <= p[j][y]) {
                    f = 1;
                    break;
                }
            if (f) continue;
            ans += (1 << i);
            for (int j = 1; j <= m; j++) mn[j] = mn2[j];
        }
        if (ans > n) ans = -3;
        cout << ans + 2 << "\n";
    }
    return 0;
}

T4

https://www.mxoj.net/problem/P110019?contestId=49

题意

给你一棵树,边有边权。每个点有 \((a_i, b_i)\)

你只能这样走:从 \(i\) 直接走到 \(j\) 花费 \(a_i + dis(i,j) \times b_i\)

求从 \(1\) 走到 \(2,3,\cdots,n\) 的最短路。

\(n\leq 10^5\)

Solution

显然有一个暴力建边然后跑 dij 的做法。

\(25\) 分。

然后考虑你啥时候换乘,肯定是从一个 \(b_i\) 大的换成一个小的。

所以你经过的点的 \(b_i\) 是递减的(除了终点)。

所以我们设 \(f_i\) 表示到 \(i\) 的最短距离,按照 \(b_i\) 从大到小转移。

\(f_u = \min_{v | b_v > b_u} \{ f_v + a_v + dis(u,v) \times b_v \}\)

然后后面就是斜率啥的,就鸽了。

posted @ 2025-08-07 16:46  Dtwww  阅读(33)  评论(0)    收藏  举报