A. Long Comparison
题意
t ≤ 1 0 4 t \le 10^4 t≤104 次查询。
给定 x 1 x_1 x1、 p 1 p_1 p1、 x 2 x_2 x2、 p 2 p_2 p2,问 x 1 × 1 0 p 1 x_1 \times 10^{p_1} x1×10p1 和 x 2 × 1 0 p 2 x_2 \times 10^{p_2} x2×10p2 之间的大小关系。
分析
如果只是单纯地进行运算后比较,但是运算可能会导致溢出。
自然我们即就可以想到,我们可以让两边同除一个 1 0 s 10^s 10s,以降低规模,有: x 1 × 1 0 p 1 / 1 0 s = x 2 × 1 0 p 2 / 1 0 s x 1 × 1 0 p 1 − s = x 2 × 1 0 p 2 − s \begin{aligned} x_1 \times 10^{p_1} / 10^s & = x_2 \times 10^{p_2} / 10^s \\ x_1 \times 10^{p_1 - s} & = x_2 \times 10^{p_2 - s} \\ \end{aligned} x1×10p1/10sx1×10p1−s=x2×10p2/10s=x2×10p2−s
取 s = min ( p 1 , p 2 ) s = \min(p_1, p_2) s=min(p1,p2) 即可使其中一个 p 1 − s p_1 - s p1−s 或者 p 2 − s p_2 - s p2−s 变为零。
不妨假设 p 1 p_1 p1 为 p 1 p_1 p1 和 p 2 p_2 p2 两者中的最小值,也就是说 s = p 1 = min ( p 1 , p 2 ) s = p_1 = \min(p_1, p_2) s=p1=min(p1,p2),那么我们就只需要比较 x 1 x_1 x1 和 x 2 × 1 0 p 2 − p 1 x_2 \times 10^{p_2 - p_1} x2×10p2−p1。令 p 2 ← p 2 − p 1 p_2 \gets p_2 - p_1 p2←p2−p1。
那么我们可以不断循环,令 p 2 ← p 2 − 1 p_2 \gets p_2 - 1 p2←p2−1, x 2 ← 10 x 2 x_2 \gets 10 x_2 x2←10x2,如果 p 2 p_2 p2 为 0 0 0 或者 x 2 x_2 x2 已经大于 x 1 x_1 x1,那么我们就退出循环,可以证明这样是不会超过范围的。最后输出答案即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
int t;
scanf("%d", &t);
while (t--) {
// 输入。
int x1, p1;
scanf("%d%d", &x1, &p1);
int x2, p2;
scanf("%d%d", &x2, &p2);
int m = min(p1, p2);
// 初次降低规模。
p1 -= m;
p2 -= m;
// 通过交换以令 p1 为 0。
int flg = true;
if (p1) {
swap(x1, x2);
swap(p1, p2);
flg = false;
}
// 循环直到 p2 == 0 或者已经 x2 > x1。
while (x2 <= x1 && p2) {
p2--;
x2 *= 10;
}
if (x2 > x1) {
// 无论 p2 是什么值,x1 < x2 * 10^p2
printf(flg ? "<" : ">");
} else if (x2 < x1) {
// 这说明 p2 == 0 一定成立,故 x1 > x2 <=> x1 > x2 * 10^p2
printf(flg ? ">" : "<");
} else {
// x1 == x2,同时 p2 == 0。
printf("=");
}
printf("\n");
}
return 0;
}
B. Absent Remainder
题意
一共有 t ≤ 1 0 4 t \le 10^4 t≤104 次查询。
给定一个序列, a = { a 1 , a 2 , … , a n } a = \{a_1, a_2, \ldots, a_n\} a={a1,a2,…,an},其中这些元素它们两两不同,一共有 n ≤ 2 × 1 0 5 n \le 2 \times 10^5 n≤2×105 个(所有查询的 n n n 的和 ∑ n \sum n ∑n 也小于等于 1 0 5 10^5 105)元素,对于任意的 i i i, 1 ≤ a i ≤ 1 0 6 1 \le a_i \le 10^6 1≤ai≤106。
我们需要得到两两不同的 ⌊ n 2 ⌋ \lfloor{n \over 2}\rfloor ⌊2n⌋ 对元素,其中每一对 ⟨ x , y ⟩ \langle x, y\rangle ⟨x,y⟩ 需要满足:
- x ≠ y x \ne y x=y。
- x x x 和 y y y 都出现在 a a a 中。
- x m o d y x \bmod y xmody 不出现在 a a a 中。
注意,一些 x x x 和 y y y 可以出现在多个对中,只需要 ⟨ x , y ⟩ \langle x, y\rangle ⟨x,y⟩ 两两不同即可。
输出一个可行的解。
分析
这是一个构造题,所以这需要我们构造答案。我们不妨令最小值为 m = min ( a 1 , a 2 , … , a n ) m = \min(a_1, a_2, \ldots, a_n) m=min(a1,a2,…,an)。
那么自然对于任意的 i i i,有 a i m o d m < m a_i \bmod m < m aimodm<m,而 a i m o d m a_i \bmod m aimodm 自然不在 a a a 中(不然 m m m 就不是最小值了)。
那么我们只需要输出 ⌊ n 2 ⌋ \lfloor{n \over 2}\rfloor ⌊2n⌋ 个 ⟨ a i ≠ m , m ⟩ \langle a_i \ne m, m\rangle ⟨ai=m,m⟩ 即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
int t;
scanf("%d", &t);
while (t--) {
// 输入。并得到最小值 m。
int n;
scanf("%d", &n);
constexpr int LEN = 2e5 + 16;
static int A[LEN];
int m = 0x3f3f3f3f;
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
m = min(m, A[i]);
}
// 输出 n / 2 个。并且要注意 a_i != m,所以我们用另外一个变量,pt 来维
// 护我们遍历到的元素。
for (int i = 0, pt = 0; i < n / 2; i++) {
while (A[pt] == m)
pt++;
printf("%d %d\n", A[pt], m);
pt++;
}
}
return 0;
}
C. Poisoned Dagger
题意
目前,我们在攻击一条龙,它的 HP 为 h ≤ 1 0 18 h \le 10^{18} h≤1018。
一共有 1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100 次攻击,第 i i i 次攻击发生在 a i a_i ai 的时刻。它会造成持续未知的 k k k 秒的 debuff,而 debuff,会导致每一秒都会减掉龙的 1 1 1 点生命值。如果它的上一次攻击造成的 debuff 还没消失,而下一次攻击就已经到来的话,debuff 也 不会 叠加,而是它先清空掉前一次的 debuff,再应用下一次攻击的 debuff。
求能打败龙的最小的 k k k。
分析
这道题,我们发现 k k k 最小可能是 1 1 1,最大可能是 1 0 18 10^{18} 1018。如此巨大的范围,自然是无法遍历的。
我们发现, k k k 越大,造成的伤害越高,也就是说从最小的有效 k k k 开始,之后比它大全是有效的,之前比它小的全是无效的。
那么二分答案即可。详情请参考参考代码。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
int t;
scanf("%d", &t);
while (t--) {
int n;
ll h;
scanf("%d%lld", &n, &h);
ll l = 1, r = 1e18;
static ll A[110];
for (int i = 0; i < n; i++) {
scanf("%lld", &A[i]);
}
// 末尾放一个无穷大的以方便处理。
A[n] = 0x3f3f3f3f3f3f3f3fLL;
// 二分。
while (l < r) {
ll mid = (l + r) / 2;
// 根据 mid,计算能造成的伤害 res。O(n) 的复杂度。
ll res = 0;
for (int i = 0; i < n; i++) {
res += min(mid, A[i + 1] - A[i]);
}
// 缩小二分的范围。
if (res < h) {
l = mid + 1;
} else {
r = mid;
}
}
printf("%lld\n", l);
}
return 0;
}
D. MEX Sequences
题意
我们定义一个序列, x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x1,x2,…,xk,对于 1 ≤ i ≤ k 1 \le i \le k 1≤i≤k,如果任意的 i i i,表达式 ∣ x i − M E X ( x 1 , x 2 , … , x i ) ∣ ≤ 1 |x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1 ∣xi−MEX(x1,x2,…,xi)∣≤1 恒成立,那么我们说这个序列是好的。
我们现在需要知道,对于一个给定的序列,有多少子序列是好的。
分析
首先我们必须得分析表达式 ∣ x i − M E X ( x 1 , x 2 , … , x i ) ∣ ≤ 1 |x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1 ∣xi−MEX(x1,x2,…,xi)∣≤1 的性质。这说明 x i x_i xi 和 M E X ( x 1 , x 2 , … , x i ) \mathrm{MEX}(x_1, x_2, \ldots, x_i) MEX(x1,x2,…,xi) 最多只相差一。
如上所示(其中红色的球代表了序列,而绿色的球表示序列对应的 M E X \mathrm{MEX} MEX 值),它一共就只有三种情况:
- 其中 P1 表示情况一。它的每一个 x i x_i xi 均满足 x i = x i − 1 x_i = x_{i - 1} xi=xi−1 或者 x i = x i − 1 + 1 x_i = x_{i - 1} +1 xi=xi−1+1。每一个 P1,它都是从 P1 所转移过来的(假设旧的 P1 的最后的坐标是 i − 1 i - 1 i−1,那么能且仅能在旧的 P1 后面添加 x i = x i − 1 x_i = x_{i - 1} xi=xi−1 或者 x i = x i − 1 + 1 x_i = x{i - 1} + 1 xi=xi−1+1 才能得到新的 P1)。
- 其中 P2 表示情况二。我们注意到,它的 x i = M E X ( x 1 , x 2 , … , x i ) + 1 x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1 xi=MEX(x1,x2,…,xi)+1。每一个 P2,都是从 P1、P2 或者 P3 转移过来的。
- 其中 P3 表示情况三。我们注意到,它的 x i = M E X ( x 1 , x 2 , … , x i ) − 1 x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) - 1 xi=MEX(x1,x2,…,xi)−1,不过和 P1 不同的是,这些序列的最高值是 M E X ( x 1 , x 2 , … , x i ) + 1 \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1 MEX(x1,x2,…,xi)+1。每一个 P3,都是从 P2 或者 P3 转移过来的。
那么我们自然可以列出转移方程,并根据转移方程来列出三种情况的好的子序列数即可。
更多细节请参考参考代码。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int MOD = 998244353;
constexpr int LEN = 5e5 + 16;
int main()
{
int t;
scanf("%d", &t);
while (t--) {
// 输入。
int n;
scanf("%d", &n);
static int A[LEN];
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
}
// 定义 dp 并全部初始化为 0。
static int dp[3][LEN];
memset(dp[0], 0, sizeof(int) * (n + 5));
memset(dp[1], 0, sizeof(int) * (n + 5));
memset(dp[2], 0, sizeof(int) * (n + 5));
for (int i = 0; i < n; i++) {
int ele = A[i];
// 状态 P1(下标为 0)由 P1 转移过来。
dp[0][ele] += (dp[0][ele] + (ele >= 1 ? dp[0][ele - 1] : 0)) % MOD;
// 状态 P2(下标为 1)由 P1、P2、P3 转移过来。)
dp[1][ele] += (dp[1][ele] + (ele >= 2 ? (dp[0][ele - 2] + dp[2][ele - 2]) % MOD : 0)) % MOD;
// 状态 P3(下标为 2)由 P2、P3 转移过来。)
dp[2][ele] += (dp[2][ele] + dp[1][ele + 2]) % MOD;
dp[0][ele] %= MOD;
dp[1][ele] %= MOD;
dp[2][ele] %= MOD;
// 如果 ele == 0,它自己就可以单独构成 P1。
if (ele == 0) {
dp[0][ele] += 1;
}
// 如果 ele == 1,它自己就可以单独构成 P2。
if (ele == 1) {
dp[1][ele] += 1;
}
}
// 累加即为解。
ll res = 0;
for (int i = 0; i <= n; i++) {
res = ((res + dp[0][i]) % MOD + dp[1][i]) % MOD + dp[2][i];
res %= MOD;
}
printf("%lld\n", res);
}
return 0;
}
E. Crazy Robot
题意
给定 n ≤ 1 0 6 n \le 10^6 n≤106 行 m ≤ 1 0 6 m \le 10^6 m≤106 列的二维地图。
地图上有且仅有一个单位格表示实验室,此外有若干个障碍物和若干个空地。
对于每一个空地,判断我们能否肯定通过操作机器人使其返回到实验室。其中操作如下:我们可以给出一个方向,然后机器人会在剩下的三个方向中选择一个方向,并前进一。如果三个方向都有障碍,它才不会行动。
对于每一个空地,我们都判断,并利用判断的结果为空地打上标记,并输出标记后的地图。
题解
我们可以观察到,对于如果一个单元格能到达的格子除了 至多一个 方向行不通的以外、其他方向都行得通的话,那么这个单元格也就是行得通的(实际中,我们只需要堵住那个行不通的即可)。反之依然。
那么我们可以使用 BFS 来求解。在 BFS 中,我们在已经证实行得通的基础上来扩展答案:
- 我们从一个未处理的、并且证实行得通的格子构成的队列中弹出来一个。命名为 s s s。
- 我们遍历 s s s 的四周。我们只遍历空格子、并且未被证实行得通的格子。假设当前遍历到了 s ′ s' s′,那么自然 s ′ s' s′ 的行得通的方向又多了一个(到 s s s)。如果行不通的方向小于等于 1 1 1 的话,那么 s ′ s' s′ 也是一个行得通的点了,不过它还没有处理,所以我们把它放在队列中。
- 处理,直到没有未处理的点。
因为行得通的点都是连通的,所以一个点如果行得通的话,那么它必然被另外的行得通的点或者实验室所 BFS 到!
参考代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d", &t);
while (t--) {
// 输入。顺便得到图书馆的坐标 (lx, ly)。
int n, m;
scanf("%d%d", &n, &m);
constexpr int LEN = 1e6 + 16;
static char S[LEN];
vector<string> MP;
int lx, ly;
for (int i = 0; i < n; i++) {
scanf("%s", S);
for (int j = 0; j < m; j++) {
if (S[j] == 'L') {
lx = i, ly = j;
}
}
MP.push_back(S);
}
// 方向。
static const int dx[] = { 0, 1, 0, -1 };
static const int dy[] = { 1, 0, -1, 0 };
// D 用来储存 (i, j) 对应的方向个数。
vector<vector<int>> D;
for (int i = 0; i < n; i++) {
D.push_back(vector<int>(m, 0));
for (int j = 0; j < m; j++) {
for (int k = 0; k < 4; k++) {
int xx = i + dx[k];
int yy = j + dy[k];
if (xx < 0 || xx >= n)
continue;
if (yy < 0 || yy >= m)
continue;
if (MP[xx][yy] == '#')
continue;
D[i][j]++;
}
}
}
// BFS。第一个是实验室。
queue<int> qx, qy;
qx.push(lx), qy.push(ly);
while (!qx.empty()) {
int x = qx.front();
qx.pop();
int y = qy.front();
qy.pop();
for (int i = 0; i < 4; i++) {
int xx = x + dx[i];
int yy = y + dy[i];
if (xx < 0 || xx >= n)
continue;
if (yy < 0 || yy >= m)
continue;
if (MP[xx][yy] != '.')
continue;
--D[xx][yy];
// 说明有小于等于一个方向能走到不可及的地方。
// 那么标记行得通,并 push 到队列中。
if (D[xx][yy] <= 1) {
MP[xx][yy] = '+';
qx.push(xx);
qy.push(yy);
}
}
}
// 输出。
for (int i = 0; i < n; i++) {
printf("%s\n", MP[i].c_str());
}
}
return 0;
}