比赛链接:点击进入比赛
A. Minimize!
思路
给出两个数 a , b ( a ≤ b ) a,b(a \le b) a,b(a≤b),要找到 c c c,使得 ( c − a ) + ( b − c ) (c-a) + (b-c) (c−a)+(b−c) 最小,发现化简后 c c c 会被消掉,简化后为 a + b a+b a+b,因此输出 a + b a+b a+b 即可。
复杂度
时间复杂度和空间复杂度均为 O ( 1 ) O(1) O(1)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int a, b;
cin >> a >> b;
cout << b - a << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
B. osu!mania
思路
按题意模拟即可,从最底行到最顶行,对于每一行找出 KaTeX parse error: Expected 'EOF', got '#' at position 2: '#̲' 在该行所在的列即可。
复杂度
时间复杂度和空间复杂度均为 O ( n 2 ) O(n^2) O(n2)
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n;
string s[505];
void solve()
{
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
for (int i = n; i >= 1; i--) {
for (int j = 0; j < 4; j++) {
if (s[i][j] == '#')
cout << j + 1 << ' ';
}
}
cout << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
C. The Legend of Freya the Frog
思路
要使得步数最少,每次跳跃点数都跳 k k k 点,直到最后可能剩下小于 k k k 点,则跳剩下的点数,因此, x x x 方向上最少步数为 ⌈ x k ⌉ \lceil \frac{x}{k} \rceil ⌈kx⌉, y y y 方向上最少步数为 ⌈ y k ⌉ \lceil \frac{y}{k} \rceil ⌈ky⌉。
因为是 x , y x,y x,y 交替方向走的,所以走 2 ∗ m a x ( ⌈ x k ⌉ , ⌈ y k ⌉ ) 2*max(\lceil \frac{x}{k} \rceil,\lceil \frac{y}{k} \rceil) 2∗max(⌈kx⌉,⌈ky⌉) 步必然能走到终点。
如果 ⌈ x k ⌉ > ⌈ y k ⌉ \lceil \frac{x}{k} \rceil > \lceil \frac{y}{k} \rceil ⌈kx⌉>⌈ky⌉,则 y y y 方向可以少走一步,最少步数为 2 ∗ m a x ( ⌈ x k ⌉ , ⌈ y k ⌉ ) − 1 2*max(\lceil \frac{x}{k} \rceil,\lceil \frac{y}{k} \rceil) -1 2∗max(⌈kx⌉,⌈ky⌉)−1,否则为 2 ∗ m a x ( ⌈ x k ⌉ , ⌈ y k ⌉ ) 2*max(\lceil \frac{x}{k} \rceil,\lceil \frac{y}{k} \rceil) 2∗max(⌈kx⌉,⌈ky⌉)。
复杂度
时间复杂度和空间复杂度均为 O ( 1 ) O(1) O(1)
代码实现
// Problem: The Legend of Freya the Frog
// Contest: Codeforces
// URL: https://m1.codeforces.com/contest/2009/problem/C?locale=en
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int x, y, k;
cin >> x >> y >> k;
int dx = (x + k - 1) / k;
int dy = (y + k - 1) / k;
if (dy >= dx) {
cout << 2 * dy << '\n';
} else {
cout << 2 * dx - 1 << '\n';
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
D. Satyam and Counting
思路
要快速判断点是否存在,只需要开个二维数组标记对应位置即可。
三角形有以下可能情况:
1,
(
a
,
0
)
,
(
a
,
1
)
,
(
b
,
0
/
1
)
(a,0),(a,1),(b,0/1)
(a,0),(a,1),(b,0/1),
a
>
b
a >b
a>b
2,
(
a
,
0
)
,
(
a
,
1
)
,
(
b
,
0
/
1
)
(a,0),(a,1),(b,0/1)
(a,0),(a,1),(b,0/1),
a
<
b
a < b
a<b
3,
(
a
,
0
)
,
(
a
−
1
,
1
)
,
(
a
+
1
,
1
)
(a,0),(a-1,1),(a+1,1)
(a,0),(a−1,1),(a+1,1)
4,
(
a
,
1
)
,
(
a
−
1
,
0
)
,
(
a
+
1
,
0
)
(a,1),(a-1,0),(a+1,0)
(a,1),(a−1,0),(a+1,0)
可以枚举 x x x 坐标 :
1,在当前 x x x 坐标下,同时存在 y = 0 , 1 y=0,1 y=0,1 的点,那么这两个点可以和 x x x 坐标不同的一个点构成直角三角形,可以关于 x x x 坐标做点数的前后缀,这样就可以快速查询前后 x x x 坐标不同的点的数量。
2,如果 ( x , y ) (x,y) (x,y) 存在, ( x − 1 , y ⊕ 1 ) , ( x + 1 , y ⊕ 1 ) (x-1,y \oplus 1),(x+1,y \oplus 1) (x−1,y⊕1),(x+1,y⊕1) 也存在,那么这三个点可以构成三角形。
复杂度
时间复杂度和空间复杂度均为 O ( n ) O(n) O(n)
代码实现
// Problem: Satyam and Counting
// Contest: Codeforces
// URL: https://m1.codeforces.com/contest/2009/problem/D?locale=en
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n;
int st[N][2], lx[N], rx[N];
void solve()
{
cin >> n;
for (int i = 0; i <= n + 1; i++) {
lx[i] = rx[i] = st[i][0] = st[i][1] = 0;
}
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
st[x][y] = 1;
// 标记对应位置
}
// 做前缀和
for (int i = 0; i <= n; i++) {
lx[i] = st[i][0] + st[i][1];
if (i)
lx[i] += lx[i - 1];
}
// 做后缀和
for (int i = n; i >= 1; i--) {
rx[i] = st[i][0] + st[i][1];
if (i < n)
rx[i] += rx[i + 1];
}
int ans = 0;
for (int i = 0; i <= n; i++) {
// (x,0),(x,1) 同时存在
if (st[i][0] && st[i][1]) {
// 加上前后的点
if (i)
ans += lx[i - 1];
if (i < n)
ans += rx[i + 1];
}
if (i > 0 && i < n) {
ans += st[i][0] && st[i - 1][1] && st[i + 1][1];
ans += st[i][1] && st[i - 1][0] && st[i + 1][0];
}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
E. Klee’s SUPER DUPER LARGE Array!!!‘
思路
y = a 1 + a 2 + . . . + a i − a i + 1 − . . . − a n y = a_1 + a_2 + ... + a_i - a_{i+1} - ... - a_n y=a1+a2+...+ai−ai+1−...−an,可以发现,如果 i i i 越小,那么 y y y 越小,反之越大,因此 y y y 关于 i i i 单调递增。
因为 a = [ k , k + 1 , . . . , k + n − 1 ] a = [k,k+1,...,k+n-1] a=[k,k+1,...,k+n−1],所以 y = k + ( k + 1 ) + . . . + ( k + i − 1 ) − ( k + i ) − ( k + i + 1 ) − . . . − ( k + n − 1 ) = i ∗ k + ( n − 1 ) ∗ i 2 − ( ( n − i ) ∗ k + ( i + n − 1 ) ∗ ( n − i ) 2 ) y = k+(k+1)+...+(k+i-1)-(k+i)-(k+i+1)-...-(k+n-1) = i*k + \frac{(n-1)*i}{2} - ((n-i)*k + \frac{(i+n-1)*(n-i)}{2}) y=k+(k+1)+...+(k+i−1)−(k+i)−(k+i+1)−...−(k+n−1)=i∗k+2(n−1)∗i−((n−i)∗k+2(i+n−1)∗(n−i))。
当 i = n i=n i=n 时, y = a 1 + . . . a n = n ∗ k + ( n − 1 ) ∗ n 2 y = a_1 + ... a_n = n*k +\frac{ (n-1)*n}{2} y=a1+...an=n∗k+2(n−1)∗n,因为 n , k > 0 n,k>0 n,k>0,所以当 i = n i = n i=n 时, y > 0 y>0 y>0。
因为 i i i 越小, y y y 越小,所以 y y y 可能会减小到 0 0 0 以下,此时 y y y 关于 i i i 的曲线就会存在零点。
离零点越近, y y y 对应的绝对值 ∣ x ∣ |x| ∣x∣就越小,因此可以二分出靠近零点的整数位置,在这个整数位置周围必然会使得 ∣ x ∣ |x| ∣x∣ 取得最小值。
或者可以直接三分,因为单调递减的曲线,取绝对值后负数的部分会对称到 x x x 轴上方,从而使得曲线存在波谷,也就是最小值点,可以三分求出该点。
复杂度
时间复杂度,二分是
log
2
(
n
)
\log_2(n)
log2(n),三分是
2
log
3
(
n
)
2\log_3(n)
2log3(n)。
空间复杂度均为
O
(
1
)
O(1)
O(1)
代码实现
- 三分写法
// Problem: Klee's SUPER DUPER LARGE Array!!!
// Contest: Codeforces
// URL: https://m1.codeforces.com/contest/2009/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, k;
int cal(int id)
{
int res = 0;
res += id * k + (id - 1) * id / 2;
res -= (n - id) * k + (id + n - 1) * (n - id) / 2;
return abs(res);
}
void solve()
{
cin >> n >> k;
int l = 0, r = n;
while (l < r) {
int lmid = l + (r - l) / 3;
int rmid = r - (r - l) / 3;
if (cal(lmid) <= cal(rmid))
r = rmid - 1;
else
l = lmid + 1;
}
cout << min(cal(l), cal(r)) << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
- 二分写法
// Problem: Klee's SUPER DUPER LARGE Array!!!
// Contest: Codeforces
// URL: https://m1.codeforces.com/contest/2009/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, k;
int cal(int id)
{
int res = 0;
res += id * k + (id - 1) * id / 2;
res -= (n - id) * k + (id + n - 1) * (n - id) / 2;
return res;
}
void solve()
{
cin >> n >> k;
int l = 0, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (cal(mid) <= 0)
l = mid;
else
r = mid - 1;
}
cout << min(abs(cal(l)), abs(cal(l + 1))) << '\n';
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
F. Firefly’s Queries
思路
把数组 a a a 向后复制一份构成新数组 b b b,可以发现从数组 b b b 下标 i ( 1 ≤ i ≤ n ) i(1 \le i \le n) i(1≤i≤n) 处开始向后取 n n n 个数构成的数组,刚好为第 i i i 个循环左移数组。
对复制后的数组 b b b 做前缀和后,这样可以便于查询第 i i i 个循环左移数组某段区间的和。
查询区间 [ l , r ] [l,r] [l,r] 的前缀和,可以分为两种情况。
1, [ l , r ] [l,r] [l,r] 完全在某个循环左移数组内,且 r r r 不处于该数组右边界,如果 [ l , r ] [l,r] [l,r] 在第 i i i 个循环左移数组内,那么 [ l , r ] [l,r] [l,r] 在数组 b b b 中的对应区间为 [ i + l − ( i − 1 ) ∗ n − 1 , i + r − ( i − 1 ) ∗ n − 1 ] [i+l-(i-1)*n-1,i+r-(i-1)*n-1] [i+l−(i−1)∗n−1,i+r−(i−1)∗n−1],直接查询对应的前缀和即为答案。
2,令满足 k ∗ n ≥ l k*n \ge l k∗n≥l 的 k k k 最大值为 l p lp lp,满足 k ∗ n ≤ r k*n \le r k∗n≤r 的 k k k 的最小值为 r p rp rp。( l p = ⌊ l n ⌋ , r p = ⌈ r n ⌉ lp = \lfloor \frac{l}{n} \rfloor,rp = \lceil \frac{r}{n} \rceil lp=⌊nl⌋,rp=⌈nr⌉ )
当 r r r 在 l l l 所在数组的右边界时, l p , r p lp,rp lp,rp 都恰好在右边界的位置,满足 l p = r p lp=rp lp=rp,若 r r r 向右移动, l p lp lp 不会移动, r p rp rp 可能会向右移动,满足 l p ≤ r p lp \le rp lp≤rp,也是不满足 l p > r p lp>rp lp>rp 的,因此只有情况 1 1 1 满足 l p > r p lp > rp lp>rp。
不考虑 l , r l,r l,r 所在的数组,中间则会有 ( r p − l p ) (rp-lp) (rp−lp) 个完整的数组,如果数组 a a a 中的总和为 s u m sum sum,那么这些数组对答案的贡献为 s u m ∗ ( r p − l p ) sum*(rp-lp) sum∗(rp−lp)。
因为 l l l 在第 l p lp lp 个循环数组,且 r r r 在 第 l p lp lp 个循环数组的右边界或者之后,即所以 [ l , r ] [l,r] [l,r] 包含了 [ l , l p ∗ n ] [l,lp*n] [l,lp∗n],这一段对答案的贡献即为数组 b b b 中 [ l p + l − ( l p − 1 ) ∗ n − 1 , l p + n − 1 ] [lp + l-(lp-1)*n - 1,lp + n - 1] [lp+l−(lp−1)∗n−1,lp+n−1] 的区间和,如果 r r r 不在某个数组的右边界,那么 [ ( r p ∗ n + 1 , r ] [(rp*n+1,r] [(rp∗n+1,r] 也会对答案产生贡献,对应的贡献为 [ r p + 1 , r p + r − r p ∗ n ] [rp+1,rp+r-rp*n] [rp+1,rp+r−rp∗n]。
复杂度
时间复杂度为
O
(
n
+
q
)
O(n+q)
O(n+q)。
O
(
n
)
O(n)
O(n) 为处理前缀和的复杂度,
q
q
q 次查询每次查询的复杂度是
O
(
1
)
O(1)
O(1) 的。
空间复杂度为 O ( n ) O(n) O(n)
代码实现
// Problem: Firefly's Queries
// Contest: Codeforces
// URL: https://m1.codeforces.com/contest/2009/problem/F
// Memory Limit: 256 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, q;
int a[3 * N];
void solve()
{
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[n + i] = a[i];
}
for (int i = 1; i <= 2 * n; i++) {
a[i] += a[i - 1];
}
while (q--) {
int l, r;
cin >> l >> r;
int lp = (l + n - 1) / n, rp = r / n;
if (lp > rp) {
int lj = rp + l - rp * n;
int rj = rp + r - rp * n;
cout << a[rj] - a[lj - 1] << '\n';
} else {
int ans = (rp - lp) * a[n];
int lj = lp - 1 + l - (lp - 1) * n;
int rj = lp - 1 + n;
ans += a[rj] - a[lj - 1];
if (r > rp * n) {
lj = rp + 1;
rj = rp + r - rp * n;
ans += a[rj] - a[lj - 1];
}
cout << ans << '\n';
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}
G1. Yunli’s Subarray Queries (easy version)
思路
如果 a i − i = a j − j a_i - i = a_j - j ai−i=aj−j,则 a j − a i = j − i a_j - a_i = j - i aj−ai=j−i,下标的距离即为这两个值的差值,则可以同时不修改 a i , a j a_i,a_j ai,aj,使得 a i , a j a_i,a_j ai,aj 存在在同一个连续子数组里面,因此可以做一个新数组 b b b, b i = a i − i b_i = a_i - i bi=ai−i。
如果 b i = b j b_i = b_j bi=bj 则, a i , a j a_i,a_j ai,aj 可以存在于同一个连续子数组里面,那么如果数组 b b b 在 [ l , r ] [l,r] [l,r] 中同一个元素的最多出现次数为 x x x,那么把 [ l , r ] [l,r] [l,r] 修改为连续子数组的最少次数即为 r − l + 1 − x r-l+1-x r−l+1−x。
因为每次查询都是查询长度为 k k k 的区间,修改为连续子数组的最少次数,所以可以处理出数组 a a a 中所有长度为 k k k 的区间修改为连续子数组的最少次数。
具体的做法是从左到右对数组 b b b 进行遍历,然后记录下 b i b_i bi 的出现次数,维护每个长度为 k k k 的窗口中元素的最大出现次数。
可以用 m u l t i e s t multiest multiest 记录所有元素出现次数的数值集合,然后用 m a p map map 记录某个元素在当前窗口的出现次数。
当遍历到第 i i i 个窗口时,新添加了 b i b_i bi 进入窗口, m a p map map 中 b i b_i bi 的出现次数加一,同时 m u l t i s e t multiset multiset 中删去 b i b_i bi 原先的出现次数,加入更新的操作次数。
用 m u l t i s e t multiset multiset 的 . r b e g i n ( ) .rbegin() .rbegin() 方法得到最大的出现次数 x x x, k − x k-x k−x 即为把 [ i , i + k − 1 ] [i,i+k-1] [i,i+k−1] 修改成连续子数组的最小操作次数。
接着,移动到第 i + 1 i+1 i+1 个窗口,要删去 b i − k + 1 b_{i-k+1} bi−k+1, m a p map map 中对应的出现次数减一, m u l t i s e t multiset multiset 也是对应的删旧增新。
这样就处理出了每个长度为 k k k 的区间修改为连续子数组的最小操作次数,开个数组记录在对应左端点或者右端点的位置,每次查询直接查即可。
复杂度
时间复杂度 O ( n log n ) O(n \log n) O(nlogn),空间复杂度 O ( n ) O(n) O(n)
代码实现
// Problem: G1. Yunli's Subarray Queries (easy version)
// Contest: Codeforces - Codeforces Round 971 (Div. 4)
// URL: https://codeforces.com/contest/2009/problem/G1
// Memory Limit: 512 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, k, q;
int a[N], f[N];
void solve()
{
cin >> n >> k >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = 1;
a[i] -= i;
}
multiset<int> all;
unordered_map<int, int> cnt;
for (int i = 1; i <= n; i++) {
int v = cnt[a[i]]++;
if (all.find(v) != all.end())
all.erase(all.find(v));
all.insert(v + 1);
if (i >= k) {
f[i] = *all.rbegin();
int w = cnt[a[i - k + 1]]--;
all.insert(w - 1);
if (all.find(w) != all.end())
all.erase(all.find(w));
}
}
while (q--) {
int l, r;
cin >> l >> r;
cout << k - f[r] << '\n';
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
}