【杂题题解】2026年05月07日
前言
这本来应该是【模拟赛总结】,但无奈题目太多了,模拟赛又没做多少。遂分到【杂题题解】。
模拟赛链接:【HT-109-MLS】核桃五一马拉松 & XRCOI Round 7。
光标移动
题目大意
给定 \(n\) 条横线,每条横线有长度 \(a_i\),其中第 \(i\) 条横线的第 \(j\) 个格子称为 \((i,j)\),现在问从 \((s_x,s_y)\) 移动到 \((e_x,e_y)\) 的最小代价。
移动操作:
- 横向移动:在第 \(i\) 行进行一次相邻位置的移动代价为 \(l_i\),位置由 \((x,y)\) 变为 \((x,y+1)\) 或 \((x,y-1)\)
- 竖向移动:从第 \(i\) 行向第 \(i+1\) 行移动一次的代价为 \(c\),位置有 \((x,y)\) 变为 \((x+1,\min\{y,a_{x+1}\})\)
题目分析
首先这题行的代价是没有用的,因为怎么移动都只会花费 \(c \times (x - a)\) 的代价,我们只需要考虑横向移动的代价即可。
注意到有些光标是要向左移动,有些是要向右移动,所以先分类讨论。
假设 \(suf_i = \min_{j=i}^{e_x} l_j\)。
向右移动
这种情况的条件是 \(suf_a < y\)。
因为与列数取最小值会抵消前面的操作,所以我们考虑在所有要取最小值的地方取完之后再向右移动,否则一定不优。
那么向右移动的条件也很显然,只有在剩下的行的代价全部大于这行代价的时候在这行移动到 \(suf_i\) 处。因为如果继续移动的话会继续和后面的行取最小值,导致操作被抵消。
实现上,我们可以先预处理出后缀的列数最小值、代价最小值,然后按照上述流程模拟即可。
时间复杂度 \(O(n)\)。
向左移动
这种情况的条件是 \(suf_a \ge y\)。
那么此时这个和列数取最小值的要求就是能帮助我们走到终点的了,所以我们显然可以先走到某一行,然后从这一行直接走到目标列,最后向下移动走到终点。因为在一行直接走到目标列一定比分多行走到目标列要优。
假设不进行横向移动时走到第 \(i\) 行的所处列数为 \(cur_i\)。
于是直接输出 \(c \times (x - a) + \min_{i=a}^x d_i \times (cur_i - y)\) 即可。
时间复杂度 \(O(n)\)。
代码实现
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 10;
int n, sx, sy, ex, ey;
int a[N], l[N], c;
int lmn[N], dmn[N];
ll ans;
void solve() {
cin >> n >> sx >> sy >> ex >> ey >> c;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> l[i];
lmn[ex] = a[ex];
dmn[ex] = l[ex];
ans = 0;
for (int i = ex - 1; i >= sx; i--) {
lmn[i] = min(lmn[i + 1], a[i]);
dmn[i] = min(dmn[i + 1], l[i]);
}
if (lmn[sx] >= ey) {
ans = 0x3f3f3f3f3f3f3f3f;
for (int i = sx, j = sy; i <= ex; i++) {
j = min(j, a[i]);
ans = min(ans, 1LL * abs(j - ey) * l[i]);
}
cout << ans + 1LL * (ex - sx) * c << '\n';
return;
}
for (int i = sx, j = sy; i <= ex; i++) {
if (min(lmn[i], ey) > j && l[i] <= dmn[i]) {
ans += 1ll * abs(min(lmn[i], ey) - j) * l[i];
j = lmn[i];
}
j = min(j, a[i]);
}
cout << ans + 1LL * (ex - sx) * c << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T; cin >> T;
while (T--) solve();
return 0;
}
等于号
题目大意
给定 \(n\) 条边,求是在加入第几条边的时候整个图出现了环。
题目分析
使用拓扑排序,如果遍历完之后还存在 \(i\) 满足 \(inv_i>0\),则说明有环。
然后求第几条边的话用二分答案就行了。
注意常数,不要和我一样写成 \(\Theta(n\log^2n)\) 了。最终时间复杂度为 \(\Theta(n\log n)\)。
省流:赛时写了强连通分量。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
struct Edge {
int x, y;
} edge[N];
int n, ind[N];
vector<int> vec, linker[N];
int get(string s) {
int res = 0;
for (int i = 0; i < s.size(); i++)
res = (s[i] - '0') + res * 10;
return res;
}
int work(int x) {
return lower_bound(vec.begin(), vec.end(), x) - vec.begin() + 1;
}
bool in_se[N];
vector<int> active;
bool check(int x) {
active.clear();
queue <int> q;
for (int i = 1; i <= x; i++) {
if (edge[i].x == edge[i].y) continue;
ind[edge[i].y]++;
linker[edge[i].x].push_back(edge[i].y);
if (!in_se[edge[i].x]) {
in_se[edge[i].x] = true;
active.push_back(edge[i].x);
}
if (!in_se[edge[i].y]) {
in_se[edge[i].y] = true;
active.push_back(edge[i].y);
}
}
for (auto i : active)
if (ind[i] == 0) q.push(i);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : linker[u]) {
ind[v]--;
if (ind[v] == 0) q.push(v);
}
}
bool have = 0;
for (auto i : active)
if (ind[i]) have = 1;
for (auto i : active) {
ind[i] = 0;
in_se[i] = false;
linker[i].clear();
}
return have;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
string x, op, y;
cin >> x >> op >> y;
int tx = get(x), ty = get(y);
edge[i] = { tx, ty };
vec.push_back(tx);
vec.push_back(ty);
}
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int i = 1; i <= n; i++) {
edge[i].x = work(edge[i].x);
edge[i].y = work(edge[i].y);
}
int l = 1, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
if (ans > 0) cout << "double\n" << ans << "\n";
else cout << "impossible\n" << -1 << "\n";
return 0;
}

浙公网安备 33010602011771号