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\)。
就是说你从 \(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 \}\)。
然后后面就是斜率啥的,就鸽了。

浙公网安备 33010602011771号