A. Long Comparison
题意
t≤104t \le 10^4t≤104 次查询。
给定 x1x_1x1、p1p_1p1、x2x_2x2、p2p_2p2,问 x1×10p1x_1 \times 10^{p_1}x1×10p1 和 x2×10p2x_2 \times 10^{p_2}x2×10p2 之间的大小关系。
分析
如果只是单纯地进行运算后比较,但是运算可能会导致溢出。
自然我们即就可以想到,我们可以让两边同除一个 10s10^s10s,以降低规模,有:x1×10p1/10s=x2×10p2/10sx1×10p1−s=x2×10p2−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(p1,p2)s = \min(p_1, p_2)s=min(p1,p2) 即可使其中一个 p1−sp_1 - sp1−s 或者 p2−sp_2 - sp2−s 变为零。
不妨假设 p1p_1p1 为 p1p_1p1 和 p2p_2p2 两者中的最小值,也就是说 s=p1=min(p1,p2)s = p_1 = \min(p_1, p_2)s=p1=min(p1,p2),那么我们就只需要比较 x1x_1x1 和 x2×10p2−p1x_2 \times 10^{p_2 - p_1}x2×10p2−p1。令 p2←p2−p1p_2 \gets p_2 - p_1p2←p2−p1。
那么我们可以不断循环,令 p2←p2−1p_2 \gets p_2 - 1p2←p2−1,x2←10x2x_2 \gets 10 x_2x2←10x2,如果 p2p_2p2 为 000 或者 x2x_2x2 已经大于 x1x_1x1,那么我们就退出循环,可以证明这样是不会超过范围的。最后输出答案即可。
参考代码
#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≤104t \le 10^4t≤104 次查询。
给定一个序列,a={a1,a2,…,an}a = \{a_1, a_2, \ldots, a_n\}a={a1,a2,…,an},其中这些元素它们两两不同,一共有 n≤2×105n \le 2 \times 10^5n≤2×105 个(所有查询的 nnn 的和 ∑n\sum n∑n 也小于等于 10510^5105)元素,对于任意的 iii,1≤ai≤1061 \le a_i \le 10^61≤ai≤106。
我们需要得到两两不同的 ⌊n2⌋\lfloor{n \over 2}\rfloor⌊2n⌋ 对元素,其中每一对 ⟨x,y⟩\langle x, y\rangle⟨x,y⟩ 需要满足:
- x≠yx \ne yx=y。
- xxx 和 yyy 都出现在 aaa 中。
- x mod yx \bmod yxmody 不出现在 aaa 中。
注意,一些 xxx 和 yyy 可以出现在多个对中,只需要 ⟨x,y⟩\langle x, y\rangle⟨x,y⟩ 两两不同即可。
输出一个可行的解。
分析
这是一个构造题,所以这需要我们构造答案。我们不妨令最小值为 m=min(a1,a2,…,an)m = \min(a_1, a_2, \ldots, a_n)m=min(a1,a2,…,an)。
那么自然对于任意的 iii,有 ai mod m<ma_i \bmod m < maimodm<m,而 ai mod ma_i \bmod maimodm 自然不在 aaa 中(不然 mmm 就不是最小值了)。
那么我们只需要输出 ⌊n2⌋\lfloor{n \over 2}\rfloor⌊2n⌋ 个 ⟨ai≠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≤1018h \le 10^{18}h≤1018。
一共有 1≤n≤1001 \le n \le 1001≤n≤100 次攻击,第 iii 次攻击发生在 aia_iai 的时刻。它会造成持续未知的 kkk 秒的 debuff,而 debuff,会导致每一秒都会减掉龙的 111 点生命值。如果它的上一次攻击造成的 debuff 还没消失,而下一次攻击就已经到来的话,debuff 也 不会 叠加,而是它先清空掉前一次的 debuff,再应用下一次攻击的 debuff。
求能打败龙的最小的 kkk。
分析
这道题,我们发现 kkk 最小可能是 111,最大可能是 101810^{18}1018。如此巨大的范围,自然是无法遍历的。
我们发现,kkk 越大,造成的伤害越高,也就是说从最小的有效 kkk 开始,之后比它大全是有效的,之前比它小的全是无效的。
那么二分答案即可。详情请参考参考代码。
参考代码
#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
题意
我们定义一个序列,x1,x2,…,xkx_1, x_2, \ldots, x_kx1,x2,…,xk,对于 1≤i≤k1 \le i \le k1≤i≤k,如果任意的 iii,表达式 ∣xi−MEX(x1,x2,…,xi)∣≤1|x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1∣xi−MEX(x1,x2,…,xi)∣≤1 恒成立,那么我们说这个序列是好的。
我们现在需要知道,对于一个给定的序列,有多少子序列是好的。
分析
首先我们必须得分析表达式 ∣xi−MEX(x1,x2,…,xi)∣≤1|x_i - \mathrm{MEX}(x_1, x_2, \ldots, x_i)| \le 1∣xi−MEX(x1,x2,…,xi)∣≤1 的性质。这说明 xix_ixi 和 MEX(x1,x2,…,xi)\mathrm{MEX}(x_1, x_2, \ldots, x_i)MEX(x1,x2,…,xi) 最多只相差一。

如上所示(其中红色的球代表了序列,而绿色的球表示序列对应的 MEX\mathrm{MEX}MEX 值),它一共就只有三种情况:
- 其中 P1 表示情况一。它的每一个 xix_ixi 均满足 xi=xi−1x_i = x_{i - 1}xi=xi−1 或者 xi=xi−1+1x_i = x_{i - 1} +1xi=xi−1+1。每一个 P1,它都是从 P1 所转移过来的(假设旧的 P1 的最后的坐标是 i−1i - 1i−1,那么能且仅能在旧的 P1 后面添加 xi=xi−1x_i = x_{i - 1}xi=xi−1 或者 xi=xi−1+1x_i = x{i - 1} + 1xi=xi−1+1 才能得到新的 P1)。
- 其中 P2 表示情况二。我们注意到,它的 xi=MEX(x1,x2,…,xi)+1x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1xi=MEX(x1,x2,…,xi)+1。每一个 P2,都是从 P1、P2 或者 P3 转移过来的。
- 其中 P3 表示情况三。我们注意到,它的 xi=MEX(x1,x2,…,xi)−1x_i = \mathrm{MEX}(x_1, x_2, \ldots, x_i) - 1xi=MEX(x1,x2,…,xi)−1,不过和 P1 不同的是,这些序列的最高值是 MEX(x1,x2,…,xi)+1\mathrm{MEX}(x_1, x_2, \ldots, x_i) + 1MEX(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≤106n \le 10^6n≤106 行 m≤106m \le 10^6m≤106 列的二维地图。
地图上有且仅有一个单位格表示实验室,此外有若干个障碍物和若干个空地。
对于每一个空地,判断我们能否肯定通过操作机器人使其返回到实验室。其中操作如下:我们可以给出一个方向,然后机器人会在剩下的三个方向中选择一个方向,并前进一。如果三个方向都有障碍,它才不会行动。
对于每一个空地,我们都判断,并利用判断的结果为空地打上标记,并输出标记后的地图。
题解
我们可以观察到,对于如果一个单元格能到达的格子除了 至多一个 方向行不通的以外、其他方向都行得通的话,那么这个单元格也就是行得通的(实际中,我们只需要堵住那个行不通的即可)。反之依然。
那么我们可以使用 BFS 来求解。在 BFS 中,我们在已经证实行得通的基础上来扩展答案:
- 我们从一个未处理的、并且证实行得通的格子构成的队列中弹出来一个。命名为 sss。
- 我们遍历 sss 的四周。我们只遍历空格子、并且未被证实行得通的格子。假设当前遍历到了 s′s's′,那么自然 s′s's′ 的行得通的方向又多了一个(到 sss)。如果行不通的方向小于等于 111 的话,那么 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;
}
本文讨论了在查询限制条件下,如何通过巧妙地调整乘法运算和选择合适的比较尺度,避免溢出,快速比较两个大数的大小。方法涉及规模缩小、循环操作和条件判断,适用于长比较和特定数值运算问题。
890

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



