zoukankan      html  css  js  c++  java
  • Codeforces Round #577 (Div. 2)

    题目链接:https://codeforces.com/contest/1201

    A - Important Exam

    送分题。

    B - Zero Array

    题意:给一个数组,每次选两个不同位置的数字,然后同时-1。问是否能全部变成0。

    题解:好像做过。首先和必须是偶数,其次最大的数字不能超过和的一半,这两个显然是必要条件,但是为什么这样就充分了呢?貌似可以归纳,每次取最大和次大同时-1,问题会变成和-2的,且最大的数字依然不超过和的一半的子问题,因为新的最大值要么是原本的最大值-1,要么原本有至少3个值都是原本的最大值,两种情况下新的最大值都不会超过和的一半。即:已知sum>=3x,求证x<=(sum-2)/2。显然当sum>=6时上式成立,当sum<6时,枚举掉这种情况即可。

    void test_case() {
        int n;
        scanf("%d", &n);
        ll sum = 0, maxa = 0;
        while(n--) {
            ll x;
            scanf("%lld", &x);
            sum += x;
            maxa = max(maxa, x);
        }
        if(sum % 2 == 1 || maxa > sum / 2)
            puts("NO");
        else
            puts("YES");
    }
    

    C - Maximum Median

    题意:给一个奇数个数的数组,每次操作可以把一个数字+1,求最多k次操作能变到的最大的中位数。

    题解:很明显有二分的做法:枚举中位数x,需要把中间及其之后的所有小于x的数变成x,求出这个花费是否超过k,求花费的时候还要再进行一次二分,带两个log。

    int n, k, mid;
    int a[200005];
    ll sum[200005];
    
    bool check(int x) {
        int pos = lower_bound(a + 1, a + 1 + n, x) - a;
        --pos;
        if(pos < mid)
            return 1;
        int cnt = pos - mid + 1;
        ll cost = 1ll * (pos - mid + 1) * x - (sum[pos] - sum[mid - 1]);
        return cost <= k;
    }
    
    void test_case() {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        for(int i = 1; i <= n; ++i)
            sum[i] = sum[i - 1] + a[i];
        mid = (n + 1) / 2;
        int L = a[mid], R = a[mid] + k;
        while(1) {
            int M = (L + R) >> 1;
            if(L == M) {
                if(L == R || check(R)) {
                    printf("%d
    ", R);
                    return;
                }
                printf("%d
    ", L);
                return;
            }
            if(check(M))
                L = M;
            else
                R = M - 1;
        }
    }
    

    但是居然T了。卡一下常数试试。最后发现是二分的时候加法溢出了。

    int n, k, mid;
    int a[200005];
    ll sum[200005];
    
    bool check(ll x) {
        int pos = lower_bound(a + 1, a + 1 + n, x) - a;
        --pos;
        if(pos < mid)
            return 1;
        ll cost = 1ll * (pos - mid + 1) * x - (sum[pos] - sum[mid - 1]);
        return cost <= k;
    }
    
    void test_case() {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        sort(a + 1, a + 1 + n);
        for(int i = 1; i <= n; ++i)
            sum[i] = sum[i - 1] + a[i];
        mid = (n + 1) / 2;
        ll L = a[mid], R = a[mid] + k;
        while(1) {
            ll M = (L + R) >> 1;
            if(L == M) {
                if(L == R || check(R)) {
                    printf("%d
    ", R);
                    return;
                }
                printf("%d
    ", L);
                return;
            }
            if(check(M))
                L = M;
            else
                R = M - 1;
        }
    }
    

    启示:上了1e9的都用ll比较好,说不定哪天就溢出了。在二分lower_bound的时候起点其实不一定要是1,直接从mid开始也可以,不过也就快了一次?

    挑战一下主体部分线性的做法,因为这题自带一个排序,所以本身的一个log是逃不掉的,思路是一次把相等的一段数字全部吃进来,然后求中位数到当前这段数字的和,加上k之后除以这段数字的个数,就得到了中位数的大小tmp。需要注意的是tmp不能超过a[r+1],因为假如超过了就应该把下一段也吃进来,这个tmp也不能小于a[r],否则就说明中位数不能到达a[r](意思是要把a[r]的一部分分给前面的数才使得大家都是tmp,这是不对的)。

    int n, k;
    ll a[200005];
    
    void test_case() {
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i)
            scanf("%lld", &a[i]);
        sort(a + 1, a + 1 + n);
        int mid = (n + 1) / 2;
        ll sum = 0, ans = a[mid];
        for(int l = mid, r; l <= n; l = r + 1) {
            for(r = l; r + 1 <= n && a[r + 1] == a[l]; ++r);
            for(int j = l; j <= r; ++j)
                sum += a[j];
            ll tmp = (k + sum) / (r - mid + 1);
            if(tmp < a[r])
                break;
            if(r != n)
                tmp = min(a[r + 1], tmp);
            ans = max(ans, tmp);
        }
        printf("%lld
    ", ans);
    }
    

    *D - Treasure Hunting

    题意:有个n*m的迷宫,从左下角开始走,迷宫中有一些宝物,要用最少的步数收集所有的宝物。每次移动只能向左向右或者向上移动,其中只有在某些安全列才能向上移动。

    题解:看起来就很dp,先去掉最高的有宝物的层,那么收集完一层之后,必定从某个安全列去往下一层,那么到达上层的每个安全列的最小步数是可以直接转移出来的。易知搜集完某层的宝物之后,分别从两侧的安全列其中之一去往上层是最优的解法。且每层的最左边或者最右边的宝物是最后被收集的,所以真正有用的安全列只有最多4个。最后特殊处理一下顶层。

    特别注意并不是从上一层的安全列上来之后就直接到某个端点,而应该先走到另一个端点。

    int n, m, k, q;
    int L[200005], R[200005];
    int LS[200005], RS[200005];
    
    struct DP {
        int pos;
        ll val;
        DP(int pos = 0, ll val = 0): pos(pos), val(val) {}
    } dp1[15], dp2[15];
    
    int dp1top, dp2top;
    
    void test_case() {
        scanf("%d%d%d%d", &n, &m, &k, &q);
        int top = 0;
        while(k--) {
            int r, c;
            scanf("%d%d", &r, &c);
            top = max(top, r);
            if(L[r] == 0) {
                L[r] = c;
                R[r] = c;
            } else {
                L[r] = min(L[r], c);
                R[r] = max(R[r], c);
            }
        }
        for(int i = 1, b; i <= q; ++i) {
            scanf("%d", &b);
            LS[b] = b;
            RS[b] = b;
        }
        for(int j = 1; j <= m; ++j) {
            if(LS[j] == 0)
                LS[j] = LS[j - 1];
        }
        for(int j = m; j >= 1; --j) {
            if(RS[j] == 0)
                RS[j] = RS[j + 1];
        }
    
        if(top == 1) {
            printf("%d
    ", R[1] - 1);
            return;
        }
        dp1top = 0;
        if(R[1] == 0)
            dp1[++dp1top] = DP(RS[1], RS[1] - 1 + 1);
        else {
            if(LS[R[1]])
                dp1[++dp1top] = DP(LS[R[1]], R[1] - 1 + R[1] - LS[R[1]] + 1);
            if(RS[R[1]])
                dp1[++dp1top] = DP(RS[R[1]], RS[R[1]] - 1 + 1);
        }
        for(int t = 2; t < top; ++t) {
            if(L[t] == 0) {
                for(int i = 1; i <= dp1top; ++i)
                    dp1[i].val += 1;
                continue;
            }
            dp2top = 0;
    
            ll Lminval = LINF;
            for(int i = 1; i <= dp1top; ++i) {
                ll dis = dp1[i].pos >= R[t] ? dp1[i].pos - L[t] : R[t] - dp1[i].pos + R[t] - L[t];
                Lminval = min(Lminval, dp1[i].val + dis);
            }
            if(LS[L[t]])
                dp2[++dp2top] = DP(LS[L[t]], Lminval + L[t] - LS[L[t]] + 1);
            if(RS[L[t]])
                dp2[++dp2top] = DP(RS[L[t]], Lminval + RS[L[t]] - L[t] + 1);
    
            ll Rminval = LINF;
            for(int i = 1; i <= dp1top; ++i) {
                ll dis = dp1[i].pos <= L[t] ? R[t] - dp1[i].pos : dp1[i].pos - L[t] + R[t] - L[t];
                Rminval = min(Rminval, dp1[i].val + dis);
            }
            if(LS[R[t]])
                dp2[++dp2top] = DP(LS[R[t]], Rminval + R[t] - LS[R[t]] + 1);
            if(RS[R[t]])
                dp2[++dp2top] = DP(RS[R[t]], Rminval + RS[R[t]] - R[t] + 1);
    
            dp1top = dp2top;
            for(int i = 1; i <= dp2top; ++i)
                dp1[i] = dp2[i];
        }
    
        ll Lminval = LINF;
        for(int i = 1; i <= dp1top; ++i) {
            ll dis = dp1[i].pos >= R[top] ? dp1[i].pos - L[top] : R[top] - dp1[i].pos + R[top] - L[top];
            Lminval = min(Lminval, dp1[i].val + dis);
        }
        ll Rminval = LINF;
        for(int i = 1; i <= dp1top; ++i) {
            ll dis = dp1[i].pos <= L[top] ? R[top] - dp1[i].pos : dp1[i].pos - L[top] + R[top] - L[top];
            Rminval = min(Rminval, dp1[i].val + dis);
        }
        printf("%lld
    ", min(Lminval, Rminval));
    }
    
  • 相关阅读:
    Curso de FP Interpretacion Lenguaje de Signos a distancia.
    T1载波与E1载波
    快速以太网中传输介质100BASETX
    MySQLdb
    NRZ编码、NRZI编码、曼彻斯特编码和差分曼彻斯特编码
    静态VLAN和动态VLAN
    Windows用脚本快速修改IP地址(Netsh)
    some skills in Windows
    shell 条件测试
    [转]不要做浮躁的嵌入式工程师
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12448054.html
Copyright © 2011-2022 走看看