20250711 总结

P4668 [BalticOI 2011] Treasures and Vikings (Day1)

题目描述

你有一张藏宝图,这张地图被划分为一个 \(N \times M\) 的网格。一个网格可能是海洋或者是岛屿的一部分。此外,地图上标出了宝藏和占据一个(海洋)方格的敌方维京船。最后,为了方便起见,你还标出了你自己的位置。

现在你必须设置一条固定的路线去获得宝藏。路线必须从你的起始位置开始,以宝藏为终点,并由一系列移动组成。在每次移动中,你只能移动到一个(水平或垂直)相邻的非岛屿方格。但要小心:维京船可能会跟随你,使用相同的移动方式!在你按照路线进行每次移动后,维京船可能会移动也可能不动。你的移动和维京船的反应合称为一轮。

在每轮之后,进行以下检查:

  • 如果你与维京船在同一条直线上(你与维京船在同一垂直或水平线上,并且中间只有海洋),你就死了。
  • 如果你没有死并且在宝藏点上,你就获得了宝藏。

编写一个程序来决定是否可以提前设置一条固定路线,使你可以通过这条路线获得宝藏,并且不会被维京人杀死——无论维京船如何移动。

输入格式

输入的第一行包含两个整数 \(N\)\(M\),表示地图的尺寸。接下来的 \(N\) 行中的每一行包含 \(M\) 个字符。每个字符描述地图上的一个方格,可以是 .(海洋)、I(岛屿的一部分)、V(维京船)、Y(你的位置)或 T(宝藏)。VYT 各出现一次。

输出格式

输出的唯一一行必须包含字符串 YES,如果可以设置一条路线来获得宝藏;否则输出 NO

输入输出样例 #1

输入 #1

5 7
Y.....V
..I....
..IIIII
.......
...T...

输出 #1

YES

输入输出样例 #2

输入 #2

5 7
Y....V.
..I....
..IIIII
.......
...T...

输出 #2

NO

输入输出样例 #3

输入 #3

2 3
.YT
VII

输出 #3

NO

说明/提示

样例解释 1

路线是:向下走三次,向右走三次,最后再向下走一次。

数据范围

对于 \(50\%\) 的数据,\(1 \le N,M \le 200\)

对于所有数据,\(1 \le N,M \le 700\)

题面翻译由 ChatGPT-4o 提供。


地图 N×MN×M 中,海域格子(包括 Y,V,T)可通行,陆地 I 不可通行。

定义海盗到每个海格的最短步数 pirDist[i][j];定义我方到每个海格的最短步数 heroDist[i][j]。

海盗 BFS

从海盗船 V 出发做一次标准 BFS,将 pirDist 初始化为无穷大,从 V 所在格子开始置 0 并入队,扩展到相邻海域,记录最短步数。

段内最小距离

海盗对同一行或同一列的追击只在“无遮挡”的连续海段内才有效——若陆地把行或列分断,则无法同回合同线袭击。

因此对每一行,扫描被陆地分隔的每段连续海域,取该段中所有格子的最小 pirDist,记到 rowSegMin[i][j]。

对每一列同理,得到 colSegMin[i][j]。

我方 BFS 与安全条件

从我方船 Y 做 BFS,每次从 (r,c)(r,c) 走到 (nr,nc)(nr,nc) 的新步数为 nd = heroDist[r][c] + 1。

若 nd < rowSegMin[nr][nc] 且 nd < colSegMin[nr][nc],则证明海盗在同一回合后最早到达该所在行段或列段的步数均比我方慢一步,不会与我同线,安全;否则不入队。

若能走到 T,答案为 “YES”,否则 “NO”。

译自 BalticOI 2011 Day1 T4「Treasures and Vikings」

你有一张藏宝图,藏宝图可视为 N×MN×M 的网格。每个格子可能是你的船、贼船、海、陆地或藏宝点。你只有一条船,整张图只有一条贼船。你和贼船都只能在海域移动。藏宝点在海中。

你与贼船交替移动,你移动一次+贼船移动一次算作一回合。每次移动,你可以移动到上下左右四个相邻格子中的一格,也可以不移动。贼船的移动同理,贼船也可以不移动。你先移动。

每一回合结束后,如果你和贼船在同一行或同一列,你就挂了;在你没挂的情况下,如果你位于藏宝点,你就拿到了宝藏。
请问:是否有一条安全的路径,使得无论贼船怎么跑你都能或者拿到宝藏。
输入格式

第一行有两个整数 NN 和 MM。

在接下来的 NN 行中,每行 MM 个字符。字符的含义如下:
字符 .. II VV YY TT
含义 海 陆地 贼船 你 藏宝点

保证只会出现表中的五种字符,保证 V, Y, TV, Y, T 都只出现一次。
输出格式

输出 YESYES 或 NONO,表示是否有一条安全的路径。

简单推一下约束

发现相邻两行,列MOD2相同的点状态一定相同

列也是如此。

所以第一行一定黑白交错,或者第一列黑白交错

然后如果满足第一行/列这样,后续行/列也会这样。

熔池

行黑白交错方案树 + 列黑白交错方案树 - 国际象棋棋盘样式的方案


题目描述

题目译自 BalticOI 2016 Day2 T3「Swap」

给定一个包含 nn 个数的序列 x1,x2,…,xnx1​,x2​,…,xn​。1,2,…,n1,2,…,n 每个数在序列中刚好出现一次。

你可以通过交换修改这个序列。你需要进行连续的 n−1n−1 轮操作,编号 k=2,3,…,nk=2,3,…,n,第 kk 轮你可以选择交换 xkxk​ 和 x⌊k/2⌋x⌊k/2⌋​ 或是什么都不做。

如果存在一个数 j(1≤j≤n)j(1≤j≤n),使得对于所有 k<jk<j 且 aj<bj,aj​<bj​, ak=bkak​=bk​ 成立,那么序列 a1…ana1​…an​ 「字典序小于」序列 b1…bnb1​…bn​。

你能得到的字典序最小的序列是什么?
输入格式

第一行,一个整数 nn。

第二行,nn 个整数,表示序列 x1,x2,…,xnx1​,x2​,…,xn​。
输出格式

输出 nn 个整数,表示你能得到的字典序最小的序列。
样例
样例输入 1

5
3 4 2 5 1

样例输出 1

2 1 3 4 5

数据范围与提示
子任务 分数 数据范围
1 10 1≤n≤201≤n≤20
2 11 1≤n≤401≤n≤40
3 27 1≤n≤10001≤n≤1000
4 20 1≤n≤5⋅1041≤n≤5⋅104
5 32 1≤n≤2⋅1051≤n≤2⋅105

当 a<min{b,c} 的时候,b,c 都没 a 小,让他们靠前字典序只会增大,于是 u 节点不交换,递归两个儿子处理儿子就好了。

当 b<min{a,c} 的时候,此时交换 a,b 让 u 节点的值最小,从而可以让字典序最小。

接下来观察子节点 ls(u),rs(u) 的顺序,现在是 a,c,但是有没有可能,会为 c,a?

一定不可能,因为交换 (ls(u),u) 的时间为 u×2,交换 (rs(u),u) 的时间为 u×2+1,所以只能先交换左节点,而后面假如交换 (rs(u),u),会让点 u 的值变为 c,与最优字典序不符,所以此情况子节点顺序只能为 a,c,继续递归两个儿子计算即可。

当 c<min{b,c} 的时候,这个时候交换 a,c 让 u 节点的值最小,才能最小化字典序,但是子节点内的顺序,有可能是 a,b 也有可能是 b,a 这个时候两种情况都有可能,并不确定哪边会更优。

不失一般性假设 a<b,假如 a 放到左子树的最后位置为 p,那么将 b 放到左子树来,位置 p 的值一定会增大,因为我们会优先让字典序更小,也就是小的在前面,此时更小的 a 都只能到位置 p,而更大的 b 自然无法到 p 以前,所以位置 p 的值一定会增大,右子树情况同理。

所以我们可以在写一个爆搜,同样按照我们上面的三条策略,搜出 a 在左子树的位置 p1 和右子树的位置 p2,之后看一下 p1,p2 谁更小,就让 b 走小的那边就好了。

然后爆搜写的是 (u,v,id) 表示当前在点 u,将 u 的值变成 v,然后上一步来自 id 的最优能到达的位置。

记得在爆搜中第三种情况的时候讨论一下 b 和 v 的大小,假如是 b 小一点,就让 v 走 b 左右子树分别走之后最终返回位置大的那一边,因为要最优化整个字典序,而不是单个字典序,b 更小,要先优化它的位置。

复杂度的话,注意到假如访问到一个点 u,爆搜中可能出现的 v 一定会是它的祖先或者某个祖先的兄弟,于是合法的 u,v 二元组只有 log 个,记忆化一下,复杂度就对了。

一堆细节。


译自 BalticOI 2019 Day2 T3. Olympiads

两个相邻的城市每年都会派出一个 KK 人的代表队参加 KK 场比赛。每一位参赛者都参加所有 KK 场比赛,单场比赛中代表队的得分是该比赛中代表队单名参赛者的最高分,而代表队的总得分是各场比赛代表队的得分之和。

举个 K=3K=3 的例子:
比赛 1 比赛 2 比赛 3
参赛者 1 4 5 3
参赛者 2 7 3 6
参赛者 3 3 4 5
团队得分 7 5 6

该队在这三场比赛的总得分为 7+5+6=187+5+6=18 。

两个城市之间已经不仅仅开始争论哪个城市有最好的代表队,而且争论哪个城市有第 CC 好的代表队。其中 C=1C=1 代表最好(即团队总分最高)的代表队, C=2C=2 代表第二好的代表队,以此类推。

你的任务是为其中一个城市找到他们城市中第 CC 好的代表队。两个代表队是不同的,当且仅当两个代表队中至少有一名成员不同。
输入格式

输入第一行包含三个整数 N,K,CN,K,C ,其中 NN 代表该城市的候选队员人数,KK 代表一个代表队的人数,CC 代表你要求出的是第 CC 好的代表队。

接下来 NN 行,每行包含 KK 个整数,其中第 ii 行第 jj 个数代表队员 ii 在第 jj 场比赛上的得分。

数据保证:K≤NK≤N ,可能的代表队组成方案数不少于 CC ,每名队员的得分不超过 106106 。
输出格式

输出包含一个整数,即该城市第 CC 好的代表队的得分。


参考K短路思想

P6230 [BalticOI 2019] 奥运会 (Day2) 题解

  1. 问题分析与建模

首先,我们来梳理一下题目要求。

输入:N个候选队员,K个比赛项目,以及一个整数C。每个队员在每个项目上都有一个得分。
目标:从N个队员中选出K个不同的队员组成一个代表队。
计分规则:
    对于每个比赛项目,代表队的“项目得分”是其K个成员在该项目上的最高分。
    代表队的“总得分”是K个项目得分的总和。
任务:找出所有可能的代表队组合中,总得分第 C 高的那个得分是多少。

一个直接的想法是:枚举所有可能的代表队组合(即从N个人中选K个人,共有 (NK)(KN​) 种组合),计算每种组合的总分,然后排序找到第C大的分数。然而,当N=500, K=6时,组合数 (5006)(6500​) 是一个天文数字,这种暴力方法显然不可行。

我们需要一个更聪明的方法,不是生成所有解,而是只生成那些分数足够高的“候选解”,直到找到第C个为止。
2. 核心思路:转化为“第 K 短路”问题

这类“寻找第 C 优解”的问题,通常可以转化为一种在状态空间中搜索的问题,类似于图论中的“第 K 短路”算法。

我们可以将每一种“构造代表队的方式”看作一个状态。我们的目标是从一个初始状态(一个队员都没选)出发,通过一系列决策(选择队员),最终构造出所有可能的代表队,并找到分数第C高的。

这个过程可以用一个优先队列(大顶堆)来管理。队列中存储的是“部分构造的”代表队方案,并根据它们能达到的 最佳可能得分 进行排序。

算法的整体流程如下:

首先,找到得分最高(最优)的那个代表队。将其得分和构造方式放入一个集合(比如visited)中,防止重复访问。将这个最优解看作第一个被找到的解。
将这个最优解的所有“邻居”状态(即只改动一个决策点而产生的次优选择)加入优先队列。
循环 C-1 次: a. 从优先队列中取出当前最优的方案。 b. 如果这个方案我们已经处理过(因为不同决策路径可能产生相同的最终队伍),就跳过它,取下一个。 c. 否则,这就是我们找到的下一个最优解(第2优,第3优,...,第C优)。记录它。 d. 将这个新找到的最优解的“邻居”状态加入优先队列。
最后一次从队列中取出的解,就是第C优解,其得分即为答案。
  1. 关键策略:定义状态与贪心构造

为了实现上述思路,我们需要解决两个关键问题:

如何高效地从一个“部分构造的方案”出发,找到它能产生的最优代表队?
如何定义一个方案的“邻居”状态,以便系统地探索所有可能性?

3.1 贪心策略与范式

原题解中提到的“排序”策略非常关键。我们可以为代表队的K个名额定义一个选择顺序,例如,我们依次为“第1个位置”、“第2个位置”,……,“第K个位置”选择队员。

一个非常有效的贪心策略是:

为第1个位置选择队员时,我们优先考虑能让第1场比赛得分最大化的队员。
确定了第1个位置的队员后,为第2个位置选择队员时,我们从剩下的人中,优先选择能让第2场比赛得分最大化的队员。
以此类推,为第i个位置选择队员时,从剩下的人中,选择能让第i场比赛得分最大化的队员。

为什么这个策略有效? 这个贪心策略为我们提供了一个从任何“部分方案”出发,构造出该方案下“最优完整方案”的方法。一个“部分方案”可以被定义为:我们已经为前j个位置确定了队员,并且限制了在第j+1个位置不能选择某个特定的“本应最优”的队员。

3.2 状态定义

根据上述分析,我们可以定义搜索过程中的一个状态 node 包含以下信息:

vector<int> V: 一个列表,存储了我们已经为代表队固定选择的队员(即决策前缀)。
bitset<505> S: 一个位图,表示哪些队员是当前可用的。初始时,所有队员都可用。当一个队员被固定(加入V)或被显式排除时,他将变为不可用。
int val: 该状态通过贪心策略补全后,能得到的代表队的总得分。
int pos: V 的长度,即已固定的前缀长度。

我们的优先队列 priority_queue 将会根据 val 对这些状态进行排序,val 最高的会优先出队。
4. 算法步骤详解

现在我们可以整合上述思路,形成完整的算法步骤。

初始化:
    创建一个空的优先队列 Q。
    创建一个初始状态 O:
        V 为空。
        S 包含所有 N 个队员。
        pos = 0。
    调用一个 calculate_best(O) 函数。该函数会: a. 接收状态 O(包含固定的前缀 V 和可用的队员集合 S)。 b. 使用前述的贪心策略,从 pos+1 到 K 依次选择队员,补全代表队。 c. 计算这个补全后的代表队的总分,存入 val。 d. 将这个补全后的完整状态(包含了完整的 V 和最终的 val)压入优先队列 Q。

主循环 (寻找第 C 解):
    循环 C 次: a. 从 Q 中弹出队首元素 x(当前已知的所有可能方案中得分最高的)。 b. x 就是我们找到的第 c 个最优解。如果我们正在寻找第 C 解,那么 x.val 就是答案。 c. 现在,我们要基于 x 生成新的、分数可能稍低一些的“邻居”状态。x 的完整队员列表是 x.V,其中从 x.pos 到 k-1 的部分是贪心选择的。 d. 遍历 i 从 x.pos 到 k-1: i. i 是贪心选择的第 i - x.pos + 1 步。在这一步,我们原本贪心地选择了队员 x.V[i]。 ii. 创建一个新状态 y,它代表了“在这一步不选择 x.V[i]”的另一种可能性。 iii. y 的状态设置为: - 固定的前缀 V 和 x 一样,但只到 i-1。 - 可用的队员集合 S:继承自 x 做决策前的状态,但明确地将队员 x.V[i] 从可用集合中移除(因为我们正在探索不选它的分支)。 iv. 调用 calculate_best(y),计算这个新分支下的最优解,并将其压入 Q。

结束: 当循环 C 次后,最后弹出的解的 val 就是我们想要的答案。

代码逻辑对照:

struct node: 对应我们定义的状态。
priority_queue<node> Q: 对应优先队列。
fix(node x) 函数: 对应 calculate_best 函数。它接收一个部分状态 x,贪心补全,计算总分,然后将完整状态压入 Q。
main 函数中的 while(c--) 循环: 对应寻找第C解的主循环。
for (int i = x.pos; i < k; ++i) 循环: 对应生成“邻居”状态的过程。通过将贪心选择的队员 x.V[i] 禁用,并重新计算该分支的最优解,来探索新的可能性。
  1. 复杂度分析

    每次从优先队列中取出一个解,我们最多会生成 K - pos (其中 pos 是固定的前缀长度,所以最多 K 个) 个新的状态。
    我们总共需要执行 C 次“取数”操作。所以最多会向队列中推入 O(CK) 个状态。
    fix 函数(即 calculate_best)的复杂度:
    计算前缀得分:O(K^2)
    贪心选择后缀:K 步,每步需要遍历 N 个队员,即 O(NK)。
    总计 O(NK)。
    优先队列操作的复杂度为 log(队列大小),即 log(CK)。

综合起来,总时间复杂度大约是 O(C * K * (NK + log(CK)))。考虑到 K 较小,这在题目给定的数据范围内是可以通过的。原题解给出的 O(nk^2C) 是一个大致的估算。

这个算法通过优先队列和巧妙的状态定义,避免了对所有组合的暴力枚举,高效地在巨大的解空间中定位到了第 C 大的解。


posted @ 2025-07-11 21:54  Dreamers_Seve  阅读(9)  评论(0)    收藏  举报