2024.9.7 提高组模拟赛题解

T1网瘾少年

解题思路

注意到从第 i i i个店走到第 i + 1 i+1 i+1个店,所花费的电量可能由某个编号 < i <i <i充电单价更优的店所充。

t i ti ti表示:第 i i i个店往后,第一个比当前充电便宜的店的编号(可以使用单调栈预处理)。

则从第 i i i个店走到第 t i ti ti个店,中间路过的所有店的充电代价都比第 i i i家店更大,故我们希望从第 i i i个店走到第 t i ti ti个店的电量都在第$$个店里充。

故可以考虑从第$ 0 个店考虑到第 0个店考虑到第 0个店考虑到第n 个店,记 个店,记 个店,记e 表示当前的电量,假设当前考虑到第 表示当前的电量,假设当前考虑到第 表示当前的电量,假设当前考虑到第i 个店:记 个店:记 个店:记dist 表示从第 表示从第 表示从第i 个店走到第 个店走到第 个店走到第ti$个店所需电量花费(可以用前缀和快速计算)。

对第 i i i个店到第 t i ti ti个店进行判定:

e > = d i s t e>=dist e>=dist,则已经足够走到第 t i ti ti个店。
e < d i s t e<dist e<dist,则目前不够走到第 t i ti ti个店,考虑充电,将电量充至 m i n ( d i s t , T ) min(dist,T) min(dist,T)
将当前的电量 e e e减去 D i Di Di(从第 i i i个店走到第 i + 1 i+1 i+1个店)。

时间复杂度 O ( n ) O(n) O(n)

参考代码

#include <bits/stdc++.h>

template <class T>
inline void read(T &x) {
	static char s;
	static bool opt;
	while (s = getchar(), (s < '0' || s > '9') && s != '-');
	x = (opt = s == '-') ? 0 : s - '0';
	while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
	if (opt) x = ~x + 1;
}

typedef long long s64;

const int N = 500100;

int n, T;

int d[N], P[N];
s64 S[N];

int top, stk[N];
int go[N];

int e;
s64 cost;

int main() {
	freopen("wayhome.in", "r", stdin);
	freopen("wayhome.out", "w", stdout);

	read(n), read(T);

	for (int i = 1; i <= n; i ++)
		read(d[i]), read(P[i]);

	bool flag = 0;
	for (int i = 1; i <= n; i ++)
		if (T < d[i]) { flag = 1; break; } // 判断无解的情况 

	if (flag) {
		puts("-1");
		return 0;
	}

	for (int i = 1; i <= n; i ++)
		S[i] = S[i - 1] + d[i];

	stk[top = 1] = n + 1;
	for (int i = n; i >= 1; i --) { // 使用单调栈维护 go[i] 
		while (top && P[i] < P[stk[top]]) top --;
		go[i] = stk[top];
		stk[++ top] = i;
	}

	for (int i = 1; i <= n; i ++) {
		s64 dist = S[go[i] - 1] - S[i - 1]; // 计算从 i 到达 go[i] 的距离 

		if (e < dist) {
			if (dist > T)
				cost += 1ll * (T - e) * P[i], e = T;
			else
				cost += 1ll * (dist - e) * P[i], e = dist;
		}

		e -= d[i];
	}

	printf("%lld\n", cost);

	return 0;
}`

T2宝石之国

解题思路

注意到两个相同宝石的最近距离,一定在相邻的两个相同宝石(即两个相同宝石之间,无同色宝石)取到。

l a s t i last_i lasti表示:第 i i i个宝石往前,第一个与第 i i i个宝石同色的宝石编号。

形式化地,对区间 [ l , r ] [l,r] [l,r]的询问,答案即为:
min ⁡ l ≤ i ≤ r , l a s t i ≥ l i − l a s t i \min_{l\leq i \leq r,last_i \geq l} {i-last_i} lir,lastilminilasti

显然,若 l a s t i ≥ l last_i\geq l lastil,则必有 i ≥ l i\geq l il,则答案为:
min ⁡ i ≤ r , l a s t i ≥ l i − l a s t i \min_{i \leq r,last_i \geq l} {i-last_i} ir,lastilminilasti

问题转化为了一个简单的二维偏序问题。

此处介绍一个简单的离线做法:暴力处理第一维 i i i,使用线段树维护第二维 l a s t i last_i lasti。将所有询问按照右端点 r r r从小到大排序,每次当右端点右移时,将所有经过的点加入。每次加入一个点 i i i时,将 i − l a s t i i-last_i ilasti插入线段树的位置 l a s t i last_i lasti中。查询时,只需查询线段树中 ≥ l \geq l l部分的最小值即可。

时间复杂度 O ( ( n + m ) l o g n ) O((n+m)log n) O((n+m)logn)

参考代码

#include <bits/stdc++.h>

template <class T>
inline void read(T &x) {
	static char s;
	static bool opt;
	while (s = getchar(), (s < '0' || s > '9') && s != '-');
	x = (opt = s == '-') ? 0 : s - '0';
	while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
	if (opt) x = ~x + 1;
}

const int N = 200100;
const int inf = 0x3f3f3f3f;

int n, m;
int a[N], b[N];

int len, mapval[N];
int turn(int x) {
	return std::lower_bound(mapval + 1, mapval + 1 + len, x) - mapval;
}

int pos[N];
int Last[N];

namespace SGT { // 线段树模板 
	struct node {
		int l, r;
		int min;
	} t[N * 4];

	void upd(int p) {
		t[p].min = std::min(t[p * 2].min, t[p * 2 + 1].min);
	}

	void build(int p, int l, int r) {
		t[p].l = l, t[p].r = r, t[p].min = inf;
		if (l == r) return;
		int mid = (l + r) >> 1;
		build(p * 2, l, mid), build(p * 2 + 1, mid + 1, r);
	}

	void change(int p, int x, int val) {
		if (t[p].l == t[p].r) {
			t[p].min = std::min(t[p].min, val);
			return;
		}
		int mid = (t[p].l + t[p].r) >> 1;
		if (x <= mid)
			change(p * 2, x, val);
		else
			change(p * 2 + 1, x, val);
		upd(p);
	}

	int ask(int p, int l, int r) {
		if (l <= t[p].l && t[p].r <= r) return t[p].min;
		int mid = (t[p].l + t[p].r) >> 1;
		int ans = inf;
		if (l <= mid)
			ans = std::min(ans, ask(p * 2, l, r));
		if (mid < r)
			ans = std::min(ans, ask(p * 2 + 1, l, r));
		return ans; 
	}
}

int ans[N];
struct query {
	int l, r, id;
	bool operator < (const query &rhs) const {
		return r < rhs.r;
	}
} q[N];

int main() {
	freopen("jewel.in", "r", stdin);
	freopen("jewel.out", "w", stdout);

	read(n), read(m);

	for (int i = 1; i <= n; i ++) read(b[i]);

	for (int i = 1; i <= n; i ++) mapval[++ len] = b[i];
	std::sort(mapval + 1, mapval + 1 + len);
	len = std::unique(mapval + 1, mapval + 1 + len) - mapval - 1;
	for (int i = 1; i <= n; i ++) a[i] = turn(b[i]); // 离散化 

	for (int i = 1; i <= n; i ++) { // 计算 last[i] 
		Last[i] = pos[a[i]];
		pos[a[i]] = i;
	}

	SGT::build(1, 1, n);

	for (int i = 1; i <= m; i ++)
		read(q[i].l), read(q[i].r), q[i].id = i;

	std::sort(q + 1, q + 1 + m);

	for (int i = 1, r = 0; i <= m; i ++) {
		while (r < q[i].r) { // 右端点右移 
			r ++;
			if (Last[r]) SGT::change(1, Last[r], r - Last[r]);
		}

		ans[q[i].id] = SGT::ask(1, q[i].l, r);
	}

	for (int i = 1; i <= m; i ++)
		printf("%d\n", ans[i] == inf ? -1 : ans[i]);

	return 0;
}

T3网格连通

解题思路

考虑先预处理出原来的 n ∗ n n*n nn正方形网格图中所有的连通块,并编号。

每次将一个 k ∗ k k*k kk的子正方形区域内的障碍物变为空白格子,本质上是将所有与该 k ∗ k k*k kk子正方形相接的连通块进行合并。故该 k ∗ k k*k kk子正方形所产生的连通块大小即为所有与该 k ∗ k k*k kk子正方形相接的连通块总大小加上 k ∗ k k*k kk子正方形中的障碍物个数

注意到所有 k ∗ k k*k kk的子正方形个数为 O ( n 2 ) O(n^2) O(n2) ,每次暴力处理一个 k ∗ k k*k kk的子正方形是 O ( k 2 ) O(k^2) O(k2)的,显然会超时。假设我们已知某一个 k ∗ k k*k kk的子正方形的信息,现在考虑将该 k ∗ k k*k kk的子正方形右移一个单位长度,则发生变动的点数为 O ( k ) O(k) O(k)级别,我们只需对这些发生变动的点进行修改即可,故每次右移一个单位长度的时间复杂度为 O ( k ) O(k) O(k)

时间复杂度 O ( n 3 ) O(n^3) O(n3)

参考代码

#include <bits/stdc++.h>

template <class T>
inline void read(T &x) {
	static char s;
	static bool opt;
	while (s = getchar(), (s < '0' || s > '9') && s != '-');
	x = (opt = s == '-') ? 0 : s - '0';
	while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
	if (opt) x = ~x + 1;
}

const int N = 510;

int n, k;

char str[N][N];

int dx[4] = { 0, +1, 0, -1 },
	dy[4] = { +1, 0, -1, 0 };

bool exist(int x, int y) {
	return 1 <= x && x <= n && 1 <= y && y <= n && str[x][y] == '.';
}

int colClock;
int col[N][N], sum[N * N];

void dfs(int x, int y, int color) { // dfs 确定每一个连通块 
	col[x][y] = color, sum[color] ++;
	for (int i = 0; i < 4; i ++) {
		int nx = x + dx[i],
			ny = y + dy[i];
		if (exist(nx, ny) && !col[nx][ny]) dfs(nx, ny, color);
	}
}

int pre[N][N]; // 二维前缀和 

int cur;
int cnt[N * N];

void add(int x) { // 加入编号为 x 的连通块 
	if (!x) return;
	if (++ cnt[x] == 1) cur += sum[x];
}

void dec(int x) { // 退出编号为 x 的连通块 
	if (!x) return;
	if (-- cnt[x] == 0) cur -= sum[x];
}

int ans;
int calc(int i, int j) { // 计算以 (i, j) 为左上角的 k * k 子矩阵的贡献 
	int ni = i + k - 1, nj = j + k - 1;
	return cur + (pre[ni][nj] - pre[i - 1][nj] - pre[ni][j - 1] + pre[i - 1][j - 1]);
}

int main() {
	freopen("grid.in", "r", stdin);
	freopen("grid.out", "w", stdout); 

	read(n), read(k);

	for (int i = 1; i <= n; i ++) scanf("%s", str[i] + 1);

	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= n; j ++)
			if (str[i][j] == '.' && !col[i][j]) dfs(i, j, ++ colClock);

	for (int i = 1; i <= n; i ++) // 计算二维前缀和 
		for (int j = 1; j <= n; j ++)
			pre[i][j] = pre[i - 1][j] + pre[i][j - 1] - pre[i - 1][j - 1]
				+ (str[i][j] == 'X');

	for (int i = 1; i + k - 1 <= n; i ++) {
		// 对左上角为 (i, 1) 的 k * k 矩阵进行初始化 

		for (int x = i; x <= i + k - 1; x ++)
			for (int y = 1; y <= k; y ++) add(col[x][y]);

		if (i > 1)
			for (int y = 1; y <= k; y ++) add(col[i - 1][y]);

		if (i + k - 1 < n)
			for (int y = 1; y <= k; y ++) add(col[i + k][y]);

		if (k < n)
			for (int x = i; x <= i + k - 1; x ++) add(col[x][k + 1]);

		ans = std::max(ans, calc(i, 1));

		for (int j = 2; j + k - 1 <= n; j ++) { // 计算每次右移的影响 
			dec(col[i - 1][j - 1]), add(col[i - 1][j + k - 1]);
			dec(col[i + k][j - 1]), add(col[i + k][j + k - 1]);

			if (j - 2 >= 1)
				for (int x = i; x <= i + k - 1; x ++) dec(col[x][j - 2]);

			if (j + k <= n)
				for (int x = i; x <= i + k - 1; x ++) add(col[x][j + k]);

			ans = std::max(ans, calc(i, j)); 
		}

		// 撤销 

		for (int x = i; x <= i + k - 1; x ++)
			for (int y = n - k + 1; y <= n; y ++) dec(col[x][y]);

		if (i > 1)
			for (int y = n - k + 1; y <= n; y ++) dec(col[i - 1][y]);

		if (i + k - 1 < n)
			for (int y = n - k + 1; y <= n; y ++) dec(col[i + k][y]);

		if (k < n)
			for (int x = i; x <= i + k - 1; x ++) dec(col[x][n - k]);
	}

	printf("%d\n", ans);

	return 0;
}

T4星际争霸

解题思路

注意到,要使得每个人的能量相等,则最后每个人的能量均为 s u m n \frac{sum}{n} nsum,其中 s u m sum sum表示所有人的能量之和。此时可以将每个人的能量减去 s u m n \frac{sum}{n} nsum,则问题转化为了使得每个人的能量均为0。

在所有能进行能量交换的人之间连边,可以证明: n n n个点可以被分成若干个互不干扰的环,环中相邻的两个点之间可以进行能量交换。我们只需对每个环取最优情况即可。

例如:样例中的a=[1,3,9,7],k=1 ,可以被拆成环1,9与环3,7。

证明:每个人 x x x连出去的边 ( x + k ) m o d n + 1 (x+k)mod n+1 (x+k)modn+1只有一条,故每个点的出度为1 。同时,任意两个不同的 x x x所对应的 ( x + k ) m o d n + 1 (x+k)mod n+1 (x+k)modn+1也是不同的,故每个点的入度为1 。同时满足这两点的图,一定是由若干个环构成的。

链的情况

首先考虑链的情况。

第一个人的能量为 a 1 a_1 a1,若要想变成 0 0 0,则他需要花费 ∣ a 1 ∣ |a_1| a1的代价将能量移向第二个人。

第二个人的能量为 a 1 + a 2 a_1+a_2 a1+a2,若想要变成 0 0 0,则他需要花费 ∣ a 1 + a 2 ∣ |a_1+a_2| a1+a2的代价将能量移向第三个人。

i i i个人的能量为 S i S_i Si,若想要变成 0 0 0,则他需要花费 S i S_i Si的代价将能量移向第 i + 1 i+1 i+1个人(其中 S i S_i Si表示前缀 i i i的和)。

故总代价为:
∑ i = 1 n ∣ S i ∣ \sum ^n_{i=1}{|S_i|} i=1nSi

环的情况

可以证明:在环中,一定存在相邻的两个点之间不进行任何能量交换。故我们可以考虑选出这两个点,断环为链。

暴力枚举断开的地方,显然会超时。形式化地,设断开的地方为 k , k + 1 k,k+1 k,k+1,则该环被拆成了一条(k+1) → \rightarrow n → \rightarrow 1 → \rightarrow k的链。考虑断环对前缀和的贡献:

k < i ≤ n k\lt i \leq n k<in时,前缀和为 S i − S k S_i-S_k SiSk
1 ≤ i ≤ k 1\leq i \leq k 1ik时,前缀和为 S i + S n − S k S_i+S_n-S_k Si+SnSk,注意到 S n = 0 S_n=0 Sn=0,则前缀和仍为 S i − S k S_i-S_k SiSk
故总代价为:
∑ i = 1 n ∣ S i − S k ∣ \sum^n_{i=1}|S_i-S_k| i=1nSiSk

为使得上式最小, S k S_k Sk S i {S_i} Si 的中位数即可。特判一下断开的地方为 n , 1 n,1 n,1的情况。

时间复杂度 O ( n l o g n ) O(n log n) O(nlogn)

参考代码

#include <bits/stdc++.h>

template <class T>
inline void read(T &x) {
	static char s;
	static bool opt;
	while (s = getchar(), (s < '0' || s > '9') && s != '-');
	x = (opt = s == '-') ? 0 : s - '0';
	while (s = getchar(), s >= '0' && s <= '9') x = x * 10 + s - '0';
	if (opt) x = ~x + 1;
}

typedef long long s64;

const int N = 500100; 

int abs(int x) {
	return x > 0 ? x : -x;
}

int n, k;
int a[N];

s64 sum;
int avg;

bool vis[N];

int len, seq[N];
s64 S[N];

s64 ans;

s64 solve() {
	for (int i = 1; i <= len; i ++) S[i] = S[i - 1] + seq[i];
	std::sort(S + 1, S + 1 + len);

	s64 sumA = 0, sumB = 0;
	for (int i = 1; i <= len; i ++) sumA += abs(S[i]); // 特判断开 len, 1 的情况 
	for (int i = 1; i <= len; i ++) sumB += abs(S[i] - S[(len + 1) / 2]); // 一般情况 

	return std::min(sumA, sumB);
}

int main() {
	freopen("energy.in", "r", stdin);
	freopen("energy.out", "w", stdout);

	read(n), read(k), k ++;

	for (int i = 1; i <= n; i ++) read(a[i]);

	for (int i = 1; i <= n; i ++) sum += a[i];
	avg = sum / n;

	for (int i = 1; i <= n; i ++) a[i] -= avg; // 第一步转化 

	for (int i = 1; i <= n; i ++) {
		if (vis[i]) continue;

		len = 0;

		int x = i;
		while (!vis[x]) { // 找环 
			seq[++ len] = a[x];
			vis[x] = 1, x = (x + k - 1) % n + 1;
		}

		ans += solve();
	}

	printf("%lld\n", ans);

	return 0;
} 
### 关于2024年12月USACO铜比赛题目解答 #### 问题背景与概述 针对2024年12月USACO铜的比赛,具体题目细节尚未公布。然而,基于以往的经验和模式,可以推测该次竞赛可能涉及的基础算法概念以及解决问题的方法。 #### 使用双数存储信息 当遇到需要多次访问同一元素的情况时,采用两个独立的数分别记录这些元素的信息是一种有效策略[^1]。这种方法特别适用于那些具有重复操作特性的场景,能够简化逻辑并提高效率。 #### 清零机制的应用 对于某些特定条件下使用的临时变量或辅助结构(比如计数器`num`),适时地将其重置为初始状态是非常重要的。这有助于防止前一次计算残留影响后续的结果准确性。 #### 时间复杂度考量 考虑到实际应用场景中的数据规模较小,即使实现较为复杂的$O(n^2)$甚至更高阶的时间复杂度算法也是可行的选择。这是因为小量级的数据集使得高时间复杂度带来的性能损耗变得微不足道。 #### 枚举法解决交互型游戏类问题 面对像“Why Did the Cow Cross the Road II”这样的互动式博弈论问题,可以通过枚举所有可能性来进行模拟推演。例如,在两轮游戏中根据不同角色的行为合来预测最终得分情况,并据此得出最优解路径[^2]。 #### 动态规划求解资源分配优化模型 以“最小化草地数量”的挑战为例,通过设定动态转移方程,逐步构建起从局部到全局的最佳解决方案框架。这里的关键在于合理定义状态表示方法及其之间的转换关系,从而确保找到满足约束条件下的最优点[^3]。 #### 特殊规则下胜负判定逻辑设计 在类似于“贝茜和朋友玩的游戏”这类含有特殊胜利条件的任务里,重点是要理解规则背后的数学原理。利用回文特性作为判断依据之一,配合其他因素综合考虑,进而形成一套完整的决策流程用于确定胜者身份[^4]。 #### 处理大规模输入输出技巧 针对较大规模的数据处理需求,如岛屿面积统计等问题,则需引入诸如离散化技术等高级手段加以应对。通过对原始坐标系内的点位进行适当变换映射成新的有序序列,既减少了内存占用又加快了运算速度[^5]。 ```python def solve_palindrome_game(T, samples): results = [] for sample in samples: stones = list(map(int, str(sample))) while sum(stones) > 0 and is_palindromic(sum(stones)): # Bessie takes palindromic number of stones first taken_by_bessie = find_largest_palindrome_less_than_or_equal_to(sum(stones)) stones -= [taken_by_bessie] if sum(stones) == 0: break # Friend then takes remaining stones which must be non-palindromic now friend_takes_rest() winner = &#39;B&#39; if sum(stones)==0 else &#39;E&#39; results.append(winner) return &#39;&#39;.join(results) def is_palindromic(num): num_str = str(abs(num)) reversed_num_str = num_str[::-1] return num_str == reversed_num_str def find_largest_palindrome_less_than_or_equal_to(max_value): for i in range(max_value, 0, -1): if is_palindromic(i): return i def friend_takes_rest(): pass # Example usage with multiple test cases represented by T=3 here. print(solve_palindrome_game(3,[7, 9, 1])) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

以太以北

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值