Codeforces Global Round 5

https://codeforces.com/contest/1237

A - Balanced Rating Changes

题意:给一堆和为0的数字,然后偶数直接除以2输出,奇数就除以2之后均匀分配上下整使得和依然是0。

题解:奇数全部取下整,然后间隔着加。注意C++里面的整数除法是向0取整,假如全部除以2的话应该是错的,可以构造这样的:

4
-3
-5
4
4

这样两个奇数都是负的,然后就多了。

void test_case() {
    int n;
    scanf("%d", &n);
    int a, b = 0;
    while(n--) {
        scanf("%d", &a);
        if(a & 1) {
            ++b;
            int t = floor(a / 2.0);
            t += (b & 1);
            printf("%d
", t);
        } else
            printf("%d
", a / 2);
    }
}

不想用浮点数的话就分正负判断吧,但是没必要,在这个4位整数的位数上面浮点数几乎没有误差。

B - Balanced Tunnel

题意:给一个入隧道的序列,一个出隧道的序列,判断谁在隧道里面超车。

题解:先把题目给的反序变成从左到右看的顺序。首先最右边的车是不可能超车的。然后其他车假如他右侧的车的出隧道后的最左值在他左边,那么他一定进行了超车。

int a[100005];
int aa[100005];
int b[100005];
int bb[100005];
int leftmost[100005];
void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &b[i]);
    reverse(a + 1, a + 1 + n);
    reverse(b + 1, b + 1 + n);
    for(int i = 1; i <= n; ++i) {
        aa[a[i]] = i;
        bb[b[i]] = i;
    }
    leftmost[n] = bb[a[n]];
    for(int i = n - 1; i >= 1; --i)
        leftmost[i] = min(leftmost[i + 1], bb[a[i]]);
    int cnt = 0;
    for(int i = 1; i <= n - 1; ++i) {
        if(leftmost[i + 1] < bb[a[i]])
            ++cnt;
    }
    printf("%d
", cnt);
}

C2 - Balanced Removals (Harder)

题意:给n个三维点,n是偶数。每次消去一对,要求每次消除的时候这两个点包围的矩形盒内没有其他点。

题解:先按z分层,然后同一层按y分条,同一条按x排序,x相邻的两两之间没有其他点全部消去,多余的至多一个点传给上层。然后接下来的y相邻的之间的矩形也不会有其他点,两两消去。

struct Point1 {
    int x, id;
    bool operator<(const Point1& p)const {
        return x < p.x;
    }
} p1[50005];

struct Point2 {
    int x, y, id;
    bool operator<(const Point2& p)const {
        return y < p.y;
    }
} p2[50005];

struct Point3 {
    int x, y, z, id;
    bool operator<(const Point3& p)const {
        return z < p.z;
    }
} p3[50005];

int solve_y(int L, int R) {
    int n = R - L + 1;
    for(int i = 1; i <= n; ++i) {
        p1[i].x = p2[L + i - 1].x;
        p1[i].id = p2[L + i - 1].id;
    }
    sort(p1 + 1, p1 + 1 + n);
    for(int i = 1; i + 1 <= n; i += 2)
        printf("%d %d
", p1[i].id, p1[i + 1].id);
    if(n & 1)
        return p1[n].id;
    return 0;
}

int solve_z(int L, int R) {
    int n = R - L + 1;
    for(int i = 1; i <= n; ++i) {
        p2[i].x = p3[L + i - 1].x;
        p2[i].y = p3[L + i - 1].y;
        p2[i].id = p3[L + i - 1].id;
    }
    sort(p2 + 1, p2 + 1 + n);
    queue<int> Q;
    int l = 1, r = 1;
    while(l <= n) {
        while(r + 1 <= n && p2[r + 1].y == p2[l].y)
            ++r;
        int p = solve_y(l, r);
        if(p) {
            Q.push(p);
            if(Q.size() == 2) {
                printf("%d", Q.front());
                Q.pop();
                printf(" %d
", Q.front());
                Q.pop();
            }
        }
        l = r + 1;
        r = l;
    }
    if(Q.size())
        return Q.front();
    return 0;
}

int n;
void solve() {
    sort(p3 + 1, p3 + 1 + n);
    int l = 1, r = 1;
    queue<int> Q;
    while(l <= n) {
        while(r + 1 <= n && p3[r + 1].z == p3[l].z)
            ++r;
        int p = solve_z(l, r);
        if(p) {
            Q.push(p);
            if(Q.size() == 2) {
                printf("%d", Q.front());
                Q.pop();
                printf(" %d
", Q.front());
                Q.pop();
            }
        }
        l = r + 1;
        r = l;
    }
}

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d%d%d", &p3[i].x, &p3[i].y, &p3[i].z);
        p3[i].id = i;
    }
    solve();
}

D - Balanced Playlist

题意:给一个音乐序列,循环播放,对于每个位置i,计算以i为起始时会听多少音乐。当你听到一首严格小于你听过的歌的cool度的最大值的一半的时候就会停止,或者报告不会停止。

题解:先观察,发现从全局最小开始听可以一直模拟出能听的最大值,一直搜索过去直到碰到底再一个一个回溯上来,或者报告听了一圈,假如听了一圈“成功”回到全局最小就说明可以一直听下去。否则回溯的时候就会把中间的所有的都更新?这样是错的,考虑下面的:

5 3 11 6

从5开始听到6就结束了,但是从6开始听到5才结束。注意到好像可以按非严格上升的序列来分组,同一组的肯定是贡献同一个结束位置的。而组与组之间有可能连上,一旦连上就会整组连上。和之前做的一场很像。但是怎么转移呢?

int n;
int a[200005], b[200005];
int l[200005], r[200005], cnt;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    a[2 * n + 1] = a[1];
    int maxai = 0, minai = INF;
    for(int i = 1; i <= n; ++i) {
        if(a[i] < minai)
            minai = a[i];
        if(a[i] > maxai)
            maxai = a[i];
    }
    if(maxai <= 2 * minai) {
        for(int i = 1; i <= n; ++i)
            printf("%d%c", -1, " 
"[i == n]);
        return;
    }
    int beg = 1;
    while(a[beg] <= a[beg + 1])
        ++beg;
    if(a[beg] > a[beg + 1])
        ++beg;
    for(int i = beg, u = 1; u <= n; ++i, ++u)
        printf("%d%c", a[i], " 
"[u == n]);
    cnt = 0;
    int cur = beg;
    while(cur - beg + 1 <= n) {
        l[++cnt] = cur, r[cnt] = cur;
        while(a[r[cnt]] <= a[r[cnt] + 1])
            ++r[cnt];
        for(int i = l[cnt]; i <= r[cnt]; ++i)
            b[i] = cnt;
        cur = r[cnt] + 1;
    }
    for(int i = beg, u = 1; u <= n; ++i, ++u)
        printf("%d%c", b[i], " 
"[u == n]);
}

组划分好了,从全局最高的开始向右一直找,找到第一个小于它的一半的,经过上面的筛选可知这样的数一定存在,那么这个全局最高的值就确定了。然后把它的整个组全部从右往左依次更新。更新完这组后,尝试打通这一组和上一组的联系。

但是好复杂耶!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i,p1,p2) for(int i=(p1);i<=(p2);++i)
#define per(i,p1,p2) for(int i=(p1);i>=(p2);--i)

const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;

int qpow(ll x, ll n) {
    ll res = 1;
    while(n) {
        if(n & 1)
            res = res * x % MOD;
        x = x * x % MOD;
        n >>= 1;
    }
    return res;
}

const int MAXN = 2e5;

/*---*/

int n;
int a[200005], b[200005], c[200005];
int l[200005], r[200005], cnt;

void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    a[2 * n + 1] = a[1];
    int maxai = 0, minai = INF;
    for(int i = 1; i <= n; ++i) {
        if(a[i] < minai)
            minai = a[i];
        if(a[i] > maxai)
            maxai = a[i];
    }
    if(maxai <= 2 * minai) {
        for(int i = 1; i <= n; ++i)
            printf("%d%c", -1, " 
"[i == n]);
        return;
    }
    int beg = 1;
    while(a[beg] <= a[beg + 1])
        ++beg;
    if(a[beg] > a[beg + 1])
        ++beg;
    for(int i = beg, u = 1; u <= n; ++i, ++u)
        printf("%d%c", a[i], " 
"[u == n]);
    cnt = 0;
    int cur = beg;
    while(cur - beg + 1 <= n) {
        l[++cnt] = cur, r[cnt] = cur;
        while(a[r[cnt]] <= a[r[cnt] + 1])
            ++r[cnt];
        for(int i = l[cnt]; i <= r[cnt]; ++i)
            b[i] = cnt;
        cur = r[cnt] + 1;
    }
    for(int i = beg, u = 1; u <= n; ++i, ++u) {
        if(i > n)
            b[i - n] = b[i];
        if(i + n <= 2 * n)
            b[i + n] = b[i];
        printf("%d%c", b[i], " 
"[u == n]);
    }
    int maxaii = beg;
    for(; a[maxaii] != maxai; ++maxaii)
        ;
    {
        int cnt = 1;
        for(int i = maxaii + 1; maxai <= 2 * a[i]; ++i)
            ++cnt;
        c[maxaii] = cnt;
        int cur = maxaii - 1, head = maxaii, rest = n - 1, curmin = maxaii;
        while(rest) {
            while(rest && b[cur] == b[head]) {
                c[cur] = ++cnt;
                curmin = min(curmin, a[cur]);
                --cur;
                if(cur == 0)
                    cur = n;
                --rest;
            }
            if(rest) {
                if(a[cur] <= 2 * curmin) {
                    c[cur] = ++cnt;
                    curmin = min(curmin, a[cur]);
                    head = cur;
                    --cur;
                    if(cur == 0)
                        cur = n;
                    --rest;
                } else {
                    c[cur] = cnt = 1;
                    curmin = a[cur];
                    head = cur;
                    --cur;
                    if(cur == 0)
                        cur = n;
                    --rest;
                }
            }
        }
    }
    for(int i = maxaii; i > n; --i)
        c[i - n] = c[i];
    for(int i = 1; i <= n; ++i)
        printf("%d%c", c[i], " 
"[i == n]);
}
10
4 3 6 4 2 4 2 4 1 9

O(n)的做法:再看一下觉得像双指针,从任意一个位置开始向右扩展,到极限之后不断向左更新,当无法再向左更新时,说明右端点实在太小了,这个时候就把右端点吐出去,直到区间中的最小值大于等于左端点的一半的下整。数据结构,就弄个双栈队列查最小值就可以了。


int n, a[100005], b[100005];
struct Queue {
    static const int MAXN = 1000000;
    static const int INF = 1061109567;
    int s1[MAXN + 5], s2[MAXN + 5];
    int s1top, s2top, s1min;

    void Clear() {
        s1top = 0;
        s2top = 0;
        s2[0] = INF;
        s1min = INF;
    }

    void Push(int x) {
        s1[++s1top] = x;
        s1min = min(s1min, x);
    }

    void Pop() {
        if(s2top)
            --s2top;
        else {
            while(s1top)
                s2[++s2top] = min(s2[s2top - 1], s1[s1top--]);
            --s2top;
            s1min = INF;
        }
    }

    int Size() {
        return s1top + s2top;
    }

    int Min() {
        return min(s2[s2top], s1min);
    }
} Q;

void test_case() {
    Q.Clear();
    scanf("%d", &n);
    int maxi = 1, mini = 1;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        if(a[i] > a[maxi])
            maxi = i;
        if(a[i] < a[mini])
            mini = i;
    }
    a[n + 1] = a[1];
    if(a[maxi] <= 2 * a[mini]) {
        for(int i = 1; i <= n; ++i)
            printf("%d%c", -1, " 
"[i == n]);
        return;
    }
    int cur = maxi;
    while(a[maxi] <= 2 * a[cur]) {
        ++cur;
        if(cur == n + 1)
            cur = 1;
    }
    --cur;
    if(cur == 0)
        cur = n;
    while(cur != maxi) {
        Q.Push(a[cur]);
        --cur;
        if(cur == 0)
            cur = n;
    };
    int rest = n;
    while(rest) {
        while(a[cur] > 2 * Q.Min())
            Q.Pop();
        Q.Push(a[cur]);
        b[cur] = Q.Size();
        --cur;
        if(cur == 0)
            cur = n;
        --rest;
    }
    for(int i = 1; i <= n; ++i)
        printf("%d%c", b[i], " 
"[i == n]);
    return;
}

假算法:假如是变成RMQ,就是对每个起点作为左端点,找一个最长的k,使得这个区间的最大值<=2*这个区间的最小值,变成一个二分然后在ST表上面找的问题,这个实现最简单,写这个。(这个做法好假啊,写出来才发现)

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12053936.html