【杂题题解】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;
}
posted @ 2026-05-12 20:39  chrispang  阅读(19)  评论(0)    收藏  举报