Codeforces Round 971 (Div. 4)

比赛链接:点击进入比赛

A. Minimize!

思路

给出两个数 a , b ( a ≤ b ) a,b(a \le b) a,b(ab),要找到 c c c,使得 ( c − a ) + ( b − c ) (c-a) + (b-c) (ca)+(bc) 最小,发现化简后 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) 2max(⌈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 2max(⌈kx,ky⌉)1,否则为 2 ∗ m a x ( ⌈ x k ⌉ , ⌈ y k ⌉ ) 2*max(\lceil \frac{x}{k} \rceil,\lceil \frac{y}{k} \rceil) 2max(⌈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),(a1,1),(a+1,1)
4, ( a , 1 ) , ( a − 1 , 0 ) , ( a + 1 , 0 ) (a,1),(a-1,0),(a+1,0) (a,1),(a1,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) (x1,y1),(x+1,y1) 也存在,那么这三个点可以构成三角形。

复杂度

时间复杂度和空间复杂度均为 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+...+aiai+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+n1],所以 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+i1)(k+i)(k+i+1)...(k+n1)=ik+2(n1)i((ni)k+2(i+n1)(ni))

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=nk+2(n1)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(1in) 处开始向后取 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(i1)n1,i+r(i1)n1],直接查询对应的前缀和即为答案。

2,令满足 k ∗ n ≥ l k*n \ge l knl k k k 最大值为 l p lp lp,满足 k ∗ n ≤ r k*n \le r knr 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 lprp,也是不满足 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) (rplp) 个完整的数组,如果数组 a a a 中的总和为 s u m sum sum,那么这些数组对答案的贡献为 s u m ∗ ( r p − l p ) sum*(rp-lp) sum(rplp)

因为 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,lpn],这一段对答案的贡献即为数组 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(lp1)n1,lp+n1] 的区间和,如果 r r r 不在某个数组的右边界,那么 [ ( r p ∗ n + 1 , r ] [(rp*n+1,r] [(rpn+1,r] 也会对答案产生贡献,对应的贡献为 [ r p + 1 , r p + r − r p ∗ n ] [rp+1,rp+r-rp*n] [rp+1,rp+rrpn]

复杂度

时间复杂度为 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 aii=ajj,则 a j − a i = j − i a_j - a_i = j - i ajai=ji,下标的距离即为这两个值的差值,则可以同时不修改 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=aii

如果 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 rl+1x

因为每次查询都是查询长度为 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 kx 即为把 [ i , i + k − 1 ] [i,i+k-1] [i,i+k1] 修改成连续子数组的最小操作次数。

接着,移动到第 i + 1 i+1 i+1 个窗口,要删去 b i − k + 1 b_{i-k+1} bik+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();
    }
}
  • 29
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值