Codeforces Round 920 (Div. 3) - 题解
A - Square
模拟
任意一点,与另外三点连线中,必有两条边(长 d d d),一条对角线(长 2 d \sqrt2 d 2d),欲求面积 S = d 2 S=d^2 S=d2,仅需对固定一点与剩下任意两点的距离平方取 m i n min min即可(必为 m i n ( d 2 , d 2 ) min(d^2,d^2) min(d2,d2)或 m i n ( d 2 , 2 d 2 ) min(d^2,2d^2) min(d2,2d2)的情况)。
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( 1 ) O(1) O(1)
#include <bits/stdc++.h>
#define dist(a,b) (x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b])
using namespace std;
int x[4], y[4];
int main() {
int t;
cin >> t;
while (t--) {
for (int i = 0; i < 4; i++) cin >> x[i] >> y[i];
cout << min(dist(1, 2), dist(1, 3)) << '\n';
}
}
B - Arranging Cats
贪心
即通过三种操作(置 0 0 0,置 1 1 1,对换),将原 01 01 01串变为目的 01 01 01串。
统计需要将 1 1 1变为 0 0 0的位数 C 1 C_1 C1,与需要将 0 0 0变为 1 1 1的位数 C 0 C_0 C0,每次对换可以将 C 0 , C 1 C_0,C_1 C0,C1各 − 1 -1 −1(如果有的话),而置 0 / 1 0/1 0/1只能使其中一个 − 1 -1 −1,故优先对换,最后 C 0 C_0 C0或 C 1 C_1 C1变为 0 0 0时,只能将另一方置 0 / 1 0/1 0/1。对换消耗 m i n ( C 0 , C 1 ) min(C_0,C_1) min(C0,C1)步,尔后的置 0 / 1 0/1 0/1消耗 ∣ C 0 − C 1 ∣ \vert C_0-C_1\vert ∣C0−C1∣,答案为 m a x ( C 0 , C 1 ) max(C_0,C_1) max(C0,C1)
- 时间复杂度: O ( n ) O(n) O(n);
- 空间复杂度: O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int a = 0, b = 0, n;
string A, B;
cin >> n >> A >> B;
for (int i = 0; i < n; i++)
if (A[i] != B[i]) {
if (A[i] == '0') a++;
else b++;
}
cout << max(a, b) << '\n';
}
}
C - Sending Messages
模拟
根据题意,在相邻两条消息的间隔时间内,我们可以选择让手机常开,此时消耗电力 a × ( m i − m i − 1 ) a\times(m_i-m_{i-1}) a×(mi−mi−1),或者关机,此时消耗电力 b b b。对每个间隔都选择消耗电量最小的选择 m i n ( a × ( m i − m i − 1 , b ) min(a\times(m_i-m_{i-1},b) min(a×(mi−mi−1,b),若剩余电量大于 0 0 0即可,反之不行。
- 时间复杂度: O ( n ) O(n) O(n);
- 空间复杂度: O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5;
int main() {
cin.tie(0)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
LL n, f, a, b, m, last = 0;
cin >> n >> f >> a >> b;
for (int i = 0; i < n; i++, last = m) {
cin >> m;
f -= min(b, (m - last) * a);
}
cout << (f > 0 ? "YES\n" : "NO\n");
}
}
D - Very Different Array
双指针,贪心
由于 D = ∑ i = 1 n ∣ a i − c i ∣ D=\sum_{i=1}^n\vert a_i-c_i\vert D=∑i=1n∣ai−ci∣,对于任意的 a i a_i ai,我们都应让 c i c_i ci尽可能大或者尽可能小,故最终 { c i } \lbrace c_i\rbrace {ci}必由 { b i } \lbrace b_i\rbrace {bi}的前缀与后缀组成,同时在考虑逐个选取 ( a i , c i ) (a_i,c_i) (ai,ci)时,对 c i c_i ci的选择一定是当前未被选择的 { b i } − { c i } \lbrace b_i\rbrace-\lbrace c_i\rbrace {bi}−{ci}中最大或最小值。
对于 ( a i , c i ) (a_i,c_i) (ai,ci),若 a i > c i a_i>c_i ai>ci,则应让 c i c_i ci尽可能小,不应出现 b j < c i b_j<c_i bj<ci且未被选取( b j ∉ { c i } b_j\not\in\lbrace c_i\rbrace bj∈{ci}),否则该 b j b_j bj显然更优,反之同理。
对于 ( a x , c x ) , ( a y , c y ) (a_x,c_x),(a_y,c_y) (ax,cx),(ay,cy),若 a x > c x , a y > c y a_x>c_x,a_y>c_y ax>cx,ay>cy,交换 c x , c y c_x,c_y cx,cy不会让答案更优,我们总是选择最大或最小值,不会对后续选择产生影响,也即没必要考虑把最值让给后续元素。
同理作用于对 a i a_i ai的选取,在选取 ( a i , c i ) (a_i,c_i) (ai,ci)时,应选择剩下中最大的 a i a_i ai匹配最小的 c i c_i ci或者最小的 a i a_i ai匹配最大的 c i c_i ci。
排序 + + +双指针即可。
- 时间复杂度: O ( n log n + m log m ) O(n\log{n}+m\log{m}) O(nlogn+mlogm)
- 空间复杂度: O ( n + m ) O(n+m) O(n+m)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5;
int a[N], b[N];
int main() {
cin.tie(0)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < m; i++) cin >> b[i];
sort(a, a + n);
sort(b, b + m);
LL res = 0, al = 0, bl = 0, ar = n - 1, br = m - 1;;
while (al <= ar)
if (abs(a[al] - b[br]) > abs(a[ar] - b[bl])) res += abs(a[al++] - b[br--]);
else res += abs(a[ar--] - b[bl++]);
cout << res << '\n';
}
}
E - Eat the Chip
博弈论?
因为二者垂直距离 ( Δ x ) (\Delta x) (Δx)每回合 − 1 -1 −1,故仅当 Δ x = 1 \Delta x=1 Δx=1时,执此手玩家可以吃掉对家从而赢得比赛,否则达成平局。由此当 Δ x \Delta x Δx为奇时Alice获胜或平局,当 Δ x \Delta x Δx为偶时Bob获胜或平局。
此时游戏变成了一场他逃她,她插翅难飞的游戏~,我们将可能取胜方记为**“追方”,不可能取胜方记为“逃方”**。
当“追方”回合时,若二者水平距离 Δ y ≤ 1 \Delta y\leq1 Δy≤1时,“逃方”便插翅难飞,他此后每一步都可以锁定“逃方”,使得“追方”移动后满足 Δ y = 0 \Delta y=0 Δy=0,直至最终“追方”获胜。
若游戏开始时 Δ y > 1 \Delta y>1 Δy>1,则“逃方”必须往两侧逃(远离“追方”一侧),在最终一步时,“追方”还无法触抵墙将“逃方”逼入死角,则平局,反之“追方”胜利。
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( 1 ) O(1) O(1)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const string ans[][2] = { {"Draw\n", "Bob\n"}, {"Draw\n", "Alice\n"} };
int main() {
cin.tie(0)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int h, w, xa, ya, xb, yb, res;
cin >> h >> w >> xa >> ya >> xb >> yb;
int dx = xb - xa;
if (dx <= 0) res = 0;
else if (dx & 1) {
if (abs(ya - yb) <= 1) res = 1;
else if (ya < yb) res = (dx / 2 + 1 >= w - ya);
else res = (dx / 2 + 1 >= ya - 1);
} else {
if (ya == yb) res = 1;
else if (ya > yb) res = (dx / 2 >= w - yb);
else res = (dx / 2 >= yb - 1);
}
cout << ans[dx & 1][res];
}
}
F - Sum of Progression
预处理,暴力
我们先抛开数据范围不谈,如果要做到 O ( 1 ) O(1) O(1)查询,我们就需要预处理:
- s [ s ] [ d ] = a s + a s + d + a s + 2 d + ⋯ + a s + ( ⌊ n − s d ⌋ − 1 ) d s[s][d]=a_s+a_{s+d}+a_{s+2d}+\cdots+a_{s+(\lfloor\frac{n-s}{d}\rfloor-1)d} s[s][d]=as+as+d+as+2d+⋯+as+(⌊dn−s⌋−1)d直至超出 n n n的范围,
- b [ s ] [ d ] = a s + a s + d × 2 + a s + 2 d × 3 + ⋯ + a s + ( ⌊ n − s d ⌋ − 1 ) d × ⌊ n − s d ⌋ b[s][d]=a_s+a_{s+d}\times2+a_{s+2d}\times3+\cdots+a_{s+(\lfloor\frac{n-s}{d}\rfloor-1)d}\times\lfloor\frac{n-s}{d}\rfloor b[s][d]=as+as+d×2+as+2d×3+⋯+as+(⌊dn−s⌋−1)d×⌊dn−s⌋直至超出 n n n的范围,
- 初始化: s [ s ] [ d ] = s [ s + d ] [ d ] + a s s[s][d]=s[s+d][d]+a_s s[s][d]=s[s+d][d]+as, b [ s ] [ d ] = s [ s ] [ d ] + b [ s + d ] [ d ] b[s][d]=s[s][d]+b[s+d][d] b[s][d]=s[s][d]+b[s+d][d],
- 查询: b [ s ] [ d ] − ( b [ s + k d ] [ d ] − k × s [ s + k d ] [ d ] ) b[s][d]-(b[s+kd][d]-k\times s[s+kd][d]) b[s][d]−(b[s+kd][d]−k×s[s+kd][d])。
然后因为 n n n的范围达到了 1 0 5 10^5 105,所以不能全部预处理,也不能挪在查询时一个个地去求解,所以……我们留“一半”预处理,“一半”查询时暴力,更形式化地,预设 λ \lambda λ,当预处理 1 ≤ d ≤ λ 1\leq d\leq \lambda 1≤d≤λ的情况,对于 d > λ d>\lambda d>λ的查询,直接暴力求解,预处理时间复杂度 O ( n λ ) O(n\lambda) O(nλ),暴力时间复杂度 O ( n λ ) O(\frac{n}{\lambda}) O(λn)。
隐去常数细节, λ \lambda λ约在 200 ≤ λ ≤ 1 0 3 200\leq\lambda\leq10^3 200≤λ≤103。
若数据全是暴力 q n λ ≤ 1 0 8 \frac{qn}{\lambda}\leq 10^8 λqn≤108,得到 200 ≤ λ 200\leq\lambda 200≤λ;同时考虑预处理时间上限 n λ ≤ 1 0 8 n\lambda\leq 10^8 nλ≤108与空间上限 n λ 8 B y t e ≤ 2 30 B y t e n\lambda 8Byte\leq 2^{30}Byte nλ8Byte≤230Byte,得到 λ ≤ 1 0 3 \lambda\leq10^3 λ≤103;
- 时间复杂度: O ( n λ + q n λ ) O(n\lambda+\frac{qn}{\lambda}) O(nλ+λqn)
- 空间复杂度: O ( n λ ) O(n\lambda) O(nλ)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int M = 200 + 1;
const int N = 1e5 + 1 + M;
LL arr[N];
LL sum[N][M], block[N][M];
int main() {
cin.tie(0)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> arr[i];
for (int i = n; i > 0; i--)
for (int d = 1; d < M; d++)
if (i + d > n) sum[i][d] = block[i][d] = arr[i];
else block[i][d] = block[i + d][d] + (sum[i][d] = sum[i + d][d] + arr[i]);
while (q--) {
int s, d, k;
cin >> s >> d >> k;
if (d < M)//前缀和O(1)
cout << block[s][d] - (s + d * k > n ? 0 : (block[s + d * k][d]) + sum[s + d * k][d] * k) << ' ';
else {//暴力O(n/M)
LL res = 0;
for (int i = 1; i <= k; i++) res += arr[s + (i - 1) * d] * i;
cout << res << ' ';
}
}
cout << '\n';
}
}
G - Mischievous Shooter
前缀和
横着、竖着、斜着、反斜着都打一遍前缀和,然后从起点开始挪动,遍历所有情况即可。
如上图第一种情况,他下挪时,在原基础上,加上下方横着部分的区间和,再减去上方反斜着部分的区间和即可。
- 时间复杂度: O ( n m ) O(nm) O(nm)
- 空间复杂度: O ( n m ) O(nm) O(nm)
//在打了~