A.弹跳游戏
题目描述
在一款弹跳游戏中,游戏主人公可以在屏幕上按照固定的距离 L 在游戏屏幕上向左或者向右跳跃。游戏屏幕可以简化为一个数轴,如果游戏主人公位于位置 X,则其向左跳跃 1 次可以到达X−L 的位置,向右跳跃 1 次可以到达 X+L 的位置。游戏中共有 N 枚金币,第 i 枚金币放置在位置 P i处,游戏主人公主只要跳跃到存在金币的位置,就能取走该位置的金币,该位置的金币被取走后,不会重新产生新的金币。游戏主人公从最初位置 X 出发,可以向左或向右进行任意多次跳跃,跳跃距离固定为 L。请你编程计算出:如果游戏主人公想要取走所有的金币,那么他的固定跳跃距离 L 的最大值是多少?
输入
第一行包含两个整数 N,X,分别表示金币的数量、游戏主人公的初始位置。第二行包含 N 个整数 P 1 ,P 2 ,…,P N ,表示每个金币的位置。
输出
输出一个整数,表示在能取走所有金币的前提下,最大的跳跃距离 L。显而易见的是,L=1 的情况下,一定能取走所有位置的金币。
样例
输入
3 3
1 7 11
输出
2
输入
3 81
33 105 57
输出
24
输入
1 1
1000000000
输出
999999999
说明:
样例 1说明
设置跳跃距离为 L=2,可以实现目标。
从位置 3 出发,向左跳跃 1 次,可以到达位置 1。
从位置 1 出发,向右跳跃 3 次,可以到达位置 7。
从位置 7 出发,向右跳跃 2 次,可以到达位置 11。
数据范围
对于 100% 的数据,满足 1≤N≤105,1≤X≤109 ,1≤P i≤10^9 。且保证,所有的 P i互不相同,X≠P i 。
10% 的数据 N=1
另外 30%的数据 X,P i ≤1000
让我们先一步步分析这个问题。
- 主人公初始位置是 X。
- 有 N N N 枚金币,位置分别是 P 1 , P 2 , … , P N P_1, P_2, \dots, P_N P1,P2,…,PN。
- 主人公可以向左或向右跳,每次跳固定的距离 L L L。
- 跳到金币位置就能收集金币,金币被取走后消失。
- 问:能收集所有金币的前提下,最大的 L L L 是多少。
如果主人公能跳到某个金币位置 ( P_i ),那么他能跳到的位置是:
X
+
k
⋅
L
X + k \cdot L
X+k⋅L
其中
k
k
k 是任意整数(正、负、零)。
也就是说,主人公能到达的位置集合是:
{
X
+
k
L
∣
k
∈
Z
}
\{ X + kL \mid k \in \mathbb{Z} \}
{X+kL∣k∈Z}
要收集所有金币,必须保证每个 ( P_i ) 都在这个集合里,即:
P
i
≡
X
(
m
o
d
L
)
P_i \equiv X \pmod{L}
Pi≡X(modL)
换句话说:
L
∣
(
P
i
−
X
)
L \mid (P_i - X)
L∣(Pi−X)
即
L
L
L 必须整除每个
(
P
i
−
X
)
(P_i - X)
(Pi−X) 的绝对值。
我们可以设
d
i
=
∣
P
i
−
X
∣
d_i = |P_i - X|
di=∣Pi−X∣。
那么条件就是:
L
∣
d
1
,
L
∣
d
2
,
…
,
L
∣
d
N
L \mid d_1, L \mid d_2, \dots, L \mid d_N
L∣d1,L∣d2,…,L∣dN
即
L
L
L 是所有
d
i
d_i
di 的公约数。
我们要找的是 最大 的
L
L
L,所以
L
L
L 应该是所有
d
i
d_i
di 的 最大公约数。因为:
- 如果 L L L 是这些差的公约数,那么主人公可以到达所有金币位置。
- 如果 L L L 是它们的最大公约数,那么它是最大的可行 L L L。
- 因为如果
L
L
L更大,它可能无法整除某个
d
i
d_i
di,那么该金币就无法到达。
所以:
L max = gcd ( ∣ P 1 − X ∣ , ∣ P 2 − X ∣ , … , ∣ P N − X ∣ ) L_{\max} = \gcd(|P_1 - X|, |P_2 - X|, \dots, |P_N - X|) Lmax=gcd(∣P1−X∣,∣P2−X∣,…,∣PN−X∣)
样例 1
N = 3 , X = 3 , P = [ 1 , 7 , 11 ] N=3, X=3, P=[1,7,11] N=3,X=3,P=[1,7,11]
差: ∣ 1 − 3 ∣ = 2 , ∣ 7 − 3 ∣ = 4 , ∣ 11 − 3 ∣ = 8 |1-3|=2, |7-3|=4, |11-3|=8 ∣1−3∣=2,∣7−3∣=4,∣11−3∣=8
g c d ( 2 , 4 , 8 ) = 2 gcd(2,4,8) = 2 gcd(2,4,8)=2 ✅
样例 2
N = 3 , X = 81 , P = [ 33 , 105 , 57 ] N=3, X=81, P=[33,105,57] N=3,X=81,P=[33,105,57]
差: ∣ 33 − 81 ∣ = 48 , ∣ 105 − 81 ∣ = 24 , ∣ 57 − 81 ∣ = 24 |33-81|=48, |105-81|=24, |57-81|=24 ∣33−81∣=48,∣105−81∣=24,∣57−81∣=24
g c d ( 48 , 24 , 24 ) = 24 gcd(48,24,24) = 24 gcd(48,24,24)=24 ✅
样例 3
N = 1 , X = 1 , P = [ 1 0 9 ] N=1, X=1, P=[10^9] N=1,X=1,P=[109]
差: 1 0 9 − 1 10^9 - 1 109−1
KaTeX parse error: Can't use function '\(' in math mode at position 5: gcd(\̲(̲ 10^9 - 1 \)) =… ✅
所以程序要做到: - 读入 N , X N, X N,X 和 P P P 数组。
- 计算每个 ∣ P i − X ∣ |P_i - X| ∣Pi−X∣。
- 求这些差值的最大公约数。
- 输出
g
c
d
gcd
gcd。
时间复杂度: O ( N + log ( max P ) ) O(N + \log(\max P)) O(N+log(maxP))
上代码!!!!!
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
long long gcd(long long a, long long b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int main() {
int N;
long long X;
cin >> N >> X;
vector<long long> P(N);
for (int i = 0; i < N; i++) {
cin >> P[i];
}
vector<long long> diffs(N);
for (int i = 0; i < N; i++) {
diffs[i] = llabs(P[i] - X);
}
long long ans = diffs[0];
for (int i = 1; i < N; i++) {
ans = gcd(ans, diffs[i]);
}
cout << ans << endl;
return 0;
}
B. 换座位
题目描述
一年级一班的教室里,有 N 个座位,座位编号为 1⋯N。有N 名同学学号为 1⋯N。开学当天,老师让第 i 名学生坐在第 i 号座位上。经过了几天的课堂学习,同学们互相熟悉了,产生了换座位的想法。每位同学给老师都写了一份自己的喜欢的座位偏好表,第 i 位同学提交的座位偏好表中有 N 个座位编号:A 1 ,A 2 ,…A n,表示该同学最希望坐到编号为 A 1的座位,如果无法实现,则第二喜欢的座位编号为 A 2 …老师收到了所有同学提交的座位偏好表之后,想请爱好编程的你,帮助同学们重新调整座位。在调整结束后,要保证每名学生最终的座位要么和原来的一样,要么是自己偏好顺序表中更靠前的座位。请编程计算出在合理的重新分配之后,每位同学有可能得到的最好的座位编号。
输入
第一行输入一个整数 N。接下来 N 行,每行包含 N 个整数:A 1 ,A2 ,…A n ,保证这是一个 1⋯N 的排列,表示对应同学的座位偏好顺序表。
输出
N 行,第 i 行输出学生 i 在重新分配后有可能得到的最好的座位编号。
样例
输入
4
1 2 3 4
1 3 2 4
1 2 3 4
1 2 3 4
输出
1
3
2
4
输入
6
1 2 3 4 5 6
3 4 5 6 1 2
4 5 6 1 2 3
5 6 1 2 3 4
6 1 2 3 4 5
2 3 4 5 6 1
输出
1
3
4
5
6
2
输入
8
4 1 6 7 2 5 8 3
3 2 4 7 5 1 6 8
8 4 7 1 3 6 2 5
7 8 1 3 4 6 2 5
5 2 7 1 3 8 6 4
1 5 6 3 4 7 2 8
7 3 8 5 4 6 2 1
4 7 1 2 6 5 8 3
输出
4
3
8
8
5
1
7
4
说明:
样例 1 说明
在这个例子中:
初始安排:学生 1 坐 1 号座位,学生 2 坐 2 号座位,学生 3 坐 3 号座位,学生 4 坐 4 号座位。
调整后的安排:学生 1 仍然坐 1 号座位,学生 2 换到 3 号座位,学生 3 换到 2 号座位,学生 4 仍然坐 4 号座位。可以看到,学生 1 和 4 没法得到更靠前的选择,而学生 2 和 3 都能换到自己更喜欢的座位。
数据范围
对于 20% 的数据,满足 N≤8。
对于 100% 的数据,满足 1≤N≤500。
让我们看一看题目:
- 初始:学生 i i i 坐在座位 i i i。
- 每个学生有一个偏好列表,是一个 1 … N 1 \dots N 1…N 的排列,表示他最喜欢的座位、第二喜欢的座位 … … …… ……直到第 N N N 喜欢的座位。
- 最终调整座位后,每个学生要么坐在原座位,要么坐在偏好列表中 比原座位更靠前 的座位(即更喜欢的座位)。
- 要求:对于每个学生
i
i
i,输出在 合理分配 下,他能得到的最好的座位编号。
“合理分配” 意味着: - 不能有两个学生坐在同一个座位。
- 每个学生的新座位必须在他的偏好列表中 比原座位更靠前或等于原座位 的位置(即要么不变,要么换到更喜欢的座位)。
分析样例:
样例 1
4
1 2 3 4
1 3 2 4
1 2 3 4
1 2 3 4
初始座位: 1 → 1 , 2 → 2 , 3 → 3 , 4 → 4 1→1, 2→2, 3→3, 4→4 1→1,2→2,3→3,4→4
- 学生1:最喜欢1号座位,本来就在1号,所以最好就是1。
- 学生2:最喜欢1号,但1号已被学生1占着(学生1不会让,因为1号是他最喜欢的),第二喜欢3号,3号目前是学生3坐着,学生3可以换到2号(因为学生3的偏好中2号比3号靠前吗?检查:学生3的偏好是 [ 1 , 2 , 3 , 4 ] [1,2,3,4] [1,2,3,4],2号是第2喜欢,3号是第3喜欢,所以可以换到更靠前的2号),这样学生2就能得到3号。
- 学生3:本来坐3号,现在换到2号,2号比3号靠前,所以可以换。
- 学生4:偏好
[
1
,
2
,
3
,
4
]
[1,2,3,4]
[1,2,3,4],4号是原座位,没有更靠前的可选,所以保持4号。
输出:
1
3
2
4
这是一个经典的 “每个人有一个可接受集合,求最优完美匹配” 问题,可以用 匹配 + 枚举 解决。
已知解法:
- 用匈牙利算法求完美匹配,但只考虑学生 i i i 和偏好列表中从第 1 1 1 个到原座位位置的那些座位之间的边。
- 为了让学生
i
i
i 得到尽可能好的座位,我们按偏好顺序尝试:
- 加入学生 i i i 与偏好座位 j j j 的边。
- 检查图中是否存在完美匹配。
- 如果存在,说明学生 i i i 可以坐在 j j j,且其他人也能合法安排。
- 于是
j
j
j 就是学生
i
i
i 能得到的最终最好座位。
对每个学生 i i i 做一次,每次 O ( N 3 ) O(N^3) O(N3),总 O ( N 4 ) O(N^4) O(N4), N = 500 N=500 N=500 时不可行。
但可以优化:二分学生 i i i 的偏好位置,检查完美匹配, O ( N 3 log N ) O(N^3 \log N) O(N3logN),可行。
让我们理一理思路:
对每个学生 i i i:
- 在偏好列表中,二分找到最好的座位位置
p
o
s
pos
pos,使得:
- 图中包含:所有学生 k 与原座位之间的边(作为保底),并且学生 i 与偏好前 pos 个座位有边,其他学生 k 与偏好前(原座位位置)的座位有边。
- 存在完美匹配。
- 这个座位就是答案。
实现时,可以:
- 构建初始二分图:每个学生 k k k 与偏好列表中前 r a n k [ k ] [ k ] rank[k][k] rank[k][k] 个座位有边( r a n k [ k ] [ k ] rank[k][k] rank[k][k] 是原座位在偏好列表中的位置,从 1 开始计数)。
- 对学生 i i i,二分 m m m:学生 i i i 只与偏好前 m m m 个座位有边,检查完美匹配。
- 找到最小的 m 使得有完美匹配,对应座位就是
p
r
e
f
[
i
]
[
m
−
1
]
pref[i][m-1]
pref[i][m−1]。
复杂度: O ( N 3 log N ) O(N^3 \log N) O(N3logN), N = 500 N=500 N=500 可过。
上代码!!!!!
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int N;
vector<vector<int>> pref;
vector<int> rankOfOriginal; // rankOfOriginal[i] 原座位 i 在学生 i 的偏好列表中的位置(从1开始)
// 匈牙利算法找完美匹配
bool dfs(const vector<vector<int>>& graph, int u, vector<bool>& visited, vector<int>& match) {
for (int v : graph[u]) {
if (!visited[v]) {
visited[v] = true;
if (match[v] == -1 || dfs(graph, match[v], visited, match)) {
match[v] = u;
return true;
}
}
}
return false;
}
// 检查是否存在完美匹配
bool hasPerfectMatch(const vector<vector<int>>& graph) {
vector<int> match(N, -1); // match[seat] = student
int count = 0;
for (int u = 0; u < N; u++) {
vector<bool> visited(N, false);
if (dfs(graph, u, visited, match)) {
count++;
}
}
return count == N;
}
int main() {
cin >> N;
pref.resize(N, vector<int>(N));
rankOfOriginal.resize(N);
// 读入数据
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
cin >> pref[i][j];
pref[i][j]--; // 转为0-based
if (pref[i][j] == i) {
rankOfOriginal[i] = j + 1; // 1-based
}
}
}
vector<int> ans(N);
// 对每个学生找最好的座位
for (int i = 0; i < N; i++) {
// 二分学生 i 的偏好位置
int low = 1, high = rankOfOriginal[i];
int best = rankOfOriginal[i];
while (low <= high) {
int mid = (low + high) / 2;
// 构建二分图
vector<vector<int>> graph(N);
for (int k = 0; k < N; k++) {
int limit = (k == i) ? mid : rankOfOriginal[k];
for (int t = 0; t < limit; t++) {
graph[k].push_back(pref[k][t]);
}
}
if (hasPerfectMatch(graph)) {
best = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
ans[i] = pref[i][best - 1] + 1; // 转回1-based输出
}
// 输出结果
for (int i = 0; i < N; i++) {
cout << ans[i] << endl;
}
return 0;
}

被折叠的 条评论
为什么被折叠?



