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

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

    A - Grasshopper And the String

    题意:有个虫子,要跳过一个长度为 (n) 的字符串(从 (0) 跳到 (n+1) ),只能停在元音字母处。求最短的跳跃距离。

    题解:求前后相邻两个停留位置的差的最大值。

    char s[200005];
    
    void test_case() {
        scanf("%s", s + 1);
        int n = strlen(s + 1);
        int lst = 0, maxlen = 0;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == 'A' || s[i] == 'E' || s[i] == 'I' || s[i] == 'O' || s[i] == 'U' || s[i] == 'Y') {
                maxlen = max(maxlen, i - lst);
                lst = i;
            }
        }
        maxlen = max(maxlen, n + 1 - lst);
        printf("%d
    ", maxlen);
    }
    

    B - Parade

    题意:阅兵,第 (i) 行的士兵有 (l_i) 个喜欢先出左脚,有 (r_i) 个喜欢先出右脚。定义阅兵的整齐度为 (|L-R|) ,其中 (L=sum_{i=1}^{n}l_i)
    (R) 同理。现在可以调整至多一个行的士兵,让他们左右颠倒,求最大的整齐度。

    题解:枚举这个行。

    int n;
    int l[100005];
    int r[100005];
    
    void test_case() {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d%d", &l[i], &r[i]);
        ll sumL = 0, sumR = 0;
        for(int i = 1; i <= n; ++i) {
            sumL += l[i];
            sumR += r[i];
        }
        int ans = 0;
        ll cur = abs(sumL - sumR);
        ll tsumL = sumL, tsumR = sumR;
        for(int i = 1; i <= n; ++i) {
            sumL -= l[i];
            sumR -= r[i];
            sumL += r[i];
            sumR += l[i];
            ll tmp = abs(sumL - sumR);
            if(tmp > cur) {
                ans = i;
                cur = tmp;
            }
            sumL = tsumL;
            sumR = tsumR;
        }
        printf("%d
    ", ans);
    }
    

    *C - Epidemic in Monstropolis

    题意:给一个数字序列 (a) ,规定每次可以选一个数字可以吃掉相邻的一个比它小的数字。再给一个数字序列 (b) ,求一种可以把 (a) 构造为 (b) 的方法。或者说明无解。

    错误算法:数字比较少,只有 (n(1leq nleq 500)) 个。暗示可以搞立方的算法。首先每个 (b_j) 对应的范围是唯一确定的,然后每次规定这一段的最大的一个吃最小的一个数字,假如最大的和最小的相等就GG了。

    题解:应该连续段内是不可以交换顺序的,所以按照敦爷的思路,枚举最后一个吃的数字,易知只有这个数字吃别的数字才是最好的,所以对于一个区间可以 (len^2) 枚举。

    但是细节还是蛮多的,肯定是贪心取连续的一段 (a_i) 直至越界或超过 (b_j) ,假如此时不为 (b_j) 则无解。除此之外虽然考虑了 (i) 提前用完,但是没有考虑 (j) 提前用完,导致搞了几发。

    int n, m;
    int a[505], b[505];
    
    bool check_help(int L, int R, int M) {
        int l = M - 1, r = M + 1;
        int sum = a[M];
        while(l >= L || r <= R) {
            if(l >= L && r <= R) {
                if(a[l] <= a[r]) {
                    if(sum <= a[l])
                        return 0;
                    else {
                        sum += a[l];
                        --l;
                    }
                } else {
                    if(sum <= a[r])
                        return 0;
                    else {
                        sum += a[r];
                        ++r;
                    }
                }
            } else if(l >= L) {
                if(sum <= a[l])
                    return 0;
                else {
                    sum += a[l];
                    --l;
                }
            } else {
                if(sum <= a[r])
                    return 0;
                else {
                    sum += a[r];
                    ++r;
                }
            }
        }
        return 1;
    }
    
    pair<int, char> ans[505];
    int atop;
    
    void gen(int L, int R, int M, int p) {
        //printf("[%d,%d],%d
    ", L, R, M);
        int l = M - 1, r = M + 1;
        int sum = a[M];
        while(l >= L || r <= R) {
            if(l >= L && r <= R) {
                if(a[l] <= a[r]) {
                    sum += a[l];
                    --l;
                    ans[++atop] = {M - p, 'L'};
                    ++p;
                } else {
                    sum += a[r];
                    ++r;
                    ans[++atop] = {M - p, 'R'};
                }
            } else if(l >= L) {
                sum += a[l];
                --l;
                ans[++atop] = {M - p, 'L'};
                ++p;
            } else {
                sum += a[r];
                ++r;
                ans[++atop] = {M - p, 'R'};
            }
        }
    }
    
    
    bool check(int L, int R, int p) {
        for(int m = L; m <= R; ++m) {
            if(check_help(L, R, m)) {
                gen(L, R, m, p);
                return 1;
            }
        }
        return 0;
    }
    
    void test_case() {
        scanf("%d", &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d", &a[i]);
        scanf("%d", &m);
        for(int j = 1; j <= m; ++j)
            scanf("%d", &b[j]);
        int i = 1, sum = 0;
        atop = 0;
        for(int j = 1; j <= m; ++j) {
            if(i > n) {
                puts("NO");
                return;
            }
            int L = i, R = i;
            while(i <= n && sum < b[j]) {
                sum += a[i];
                R = i;
                ++i;
            }
            if(sum != b[j]) {
                puts("NO");
                return;
            }
            if(!check(L, R, L - j)) {
                puts("NO");
                return;
            }
            sum = 0;
        }
        if(i <= n) {
            puts("NO");
            return;
        }
        puts("YES");
        for(int i = 1; i <= atop; ++i)
            printf("%d %c
    ", ans[i].first, ans[i].second);
    }
    

    收获:只有移除左侧的元素才会使得中间元素的偏移值变大。注意观察一整段移除元素之后偏移值的变化。以及验证和构造可以分开写,加快常数也减少复杂度。

    D - Kostya the Sculptor

    题意:找至多两块可以完全贴合(某个面完全一致)的长方体,贴在一起,然后切一个最大的球出来。

    题解:考虑要切的是球体,所以受限制的是最短边。所以可以把边排序,把长的两条边作为ID,然后尝试把短边贴在一起,这样有可能会改进答案。所以选个数据结构map就可以。甚至连常数都可以省掉,定义一个struct的前两维是长边,然后按照长边排序,可以贴合的就会排在附近,找出每种前两维相等的struct中最大的两个进行贴合。

    int a[3];
    map<pair<int, int>, pair<int, int> > M;
    
    void test_case() {
        int n;
        scanf("%d", &n);
        int ansL = 0;
        int k = 0;
        int k1 = 0, k2 = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d%d", &a[0], &a[1], &a[2]);
            sort(a, a + 3);
            pii &tL = M[{a[1], a[2]}];
            if(tL.first == 0) {
                if(a[0] > ansL) {
                    ansL = a[0];
                    k = 1;
                    k1 = i;
                }
                tL = {a[0], i};
            } else {
                int curL = min(a[1], a[0] + tL.first);
                if(curL > ansL) {
                    ansL = curL;
                    k = 2;
                    k1 = i;
                    k2 = tL.second;
                }
                if(a[0] > tL.first)
                    tL = {a[0], i};
            }
        }
        printf("%d
    ", k);
        if(k == 1)
            printf("%d
    ", k1);
        else
            printf("%d %d
    ", k1, k2);
    }
    

    *F - Drivers Dissatisfaction

    群友们好像提到NOI魔法森林。

    题意:有一个 (n(2leq nleq 2cdot 10^5))(m(n-1leq mleq 2cdot 10^5)) 边的连通无向边带权图。每条边除了权值 (w_j) 外还带有减少 (w_j) 一个单位的cost (c_j) 。用不超过 (S) 的cost,构造一棵mst。注意边权可以为负数或0。

    题解:花钱减少的权值的边肯定是选一条既在mst里面的且单价最便宜的边。所以每条边事实上只有两种状态,原价或者最便宜价。那么枚举每条边作为最便宜的,然后这条边肯定必选,剩下的原价边求一次mst。这个复杂度是 (m^2logm) 。枚举的过程中有可能可以重复利用的就是原价的mst,所以怪不得是LCT。假如得到一棵原价mst,然后加入最便宜边的时候首先若是其原价边本身在mst中则直接计算新贡献即可,否则一定断开这条链上的原价最大值,然后连接最便宜边得到新的(可能的)mst。那么这个并不需要LCT,树剖也可以。但是其实树剖是可以带修改的,这道题不带修改,所以按道理用树剖的思路把一条链断成 (logn) 个连续区间,然后在ST表上面查就可以了。正好备一个树剖ST表。

    看见是4秒,可以先写一个树剖线段树。但是最后还是直接树剖ST表过了。

    需要注意的细节是,ST表需要传递最值来源于哪里。而且要临时“发明”一个对边树剖。注意对边树剖时,定下根之后除了根以外的每个节点和它与其父亲的边一一对应。在树剖上统计的时候,当在同一重链上时则不计算最高的节点,轻重链交替时要计算最高的节点。

    #define lc (o<<1)
    #define rc (o<<1|1)
    
    const int MAXN = 200000 + 5;
    int dep[MAXN], siz[MAXN],  son[MAXN], fa[MAXN], top[MAXN], tid[MAXN], rnk[MAXN], cnt;
    
    int n, m, r, mod;
    int a[MAXN];
    
    int head[MAXN], etop;
    
    struct Edge {
        int v, next;
        int w, i;
    } e[MAXN * 2];
    
    inline void init(int n) {
        etop = 0;
        memset(head, -1, sizeof(head[0]) * (n + 1));
    }
    
    inline void addedge(int u, int v, int w, int i) {
        e[++etop].v = v;
        e[etop].w = w;
        e[etop].i = i;
        e[etop].next = head[u];
        head[u] = etop;
        e[++etop].v = u;
        e[etop].w = w;
        e[etop].i = i;
        e[etop].next = head[v];
        head[v] = etop;
    }
    
    pii faE[MAXN + 5];
    
    struct SparseTable {
        static const int MAXLOGN = 19;
        static const int MAXN = 200000;
        int n, logn[MAXN + 5];
        pii f[MAXN + 5][MAXLOGN + 1];
    
        void Init1() {
            logn[1] = 0;
            for(int i = 2; i <= MAXN; i++)
                logn[i] = logn[i >> 1] + 1;
        }
    
        void Init2(int _n) {
            n = _n;
            for(int i = 1; i <= n; i++)
                //树剖时应替换本身在线段树中build位置的东西
                f[i][0] = faE[rnk[i]];
            for(int j = 1, maxlogn = logn[n]; j <= maxlogn; j++) {
                for(int i = 1; i + (1 << j) - 1 <= n; i++)
                    f[i][j] = max(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
            }
        }
    
        pii Query(int l, int r) {
            //一个可以返回最大值以及最大值来源(最右)的ST表
            int s = logn[r - l + 1];
            return max(f[l][s], f[r - (1 << s) + 1][s]);
        }
    } st;
    
    void init1() {
        dep[r] = 1;
    }
    
    void dfs1(int u, int t, pii E) {
        siz[u] = 1, son[u] = -1, fa[u] = t;
        faE[u] = E;
        for (int i = head[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if(v == t)
                continue;
            dep[v] = dep[u] + 1;
            dfs1(v, u, {e[i].w, e[i].i});
            siz[u] += siz[v];
            if (son[u] == -1 || siz[v] > siz[son[u]])
                son[u] = v;
        }
    }
    
    void init2() {
        cnt = 0;
    }
    
    void dfs2(int u, int t) {
        top[u] = t;
        tid[u] = ++cnt;
        rnk[cnt] = u;
        if (son[u] == -1)
            return;
        dfs2(son[u], t);
        for (int i = head[u]; i != -1; i = e[i].next) {
            int v = e[i].v;
            if(v == fa[u] || v == son[u])
                continue;
            dfs2(v, v);
        }
    }
    
    pii CPTQuery(int u, int v) {
        pii ret = {-INF, -INF};
        int tu = top[u], tv = top[v];
        while (tu != tv) {
            if(dep[tu] > dep[tv]) {
                swap(u, v);
                tu = top[u], tv = top[v];
            }
            //对边树剖,没到同一重链的时候,要算上轻重链交错位置
            ret = max(ret, st.Query(tid[tv], tid[v]));
            v = fa[tv];
            tv = top[v];
        }
        //对边树剖,已到达同一重链上,不再算上高处的节点对应的边,也就是左区间+1,越界需要返回
        if(tid[u] == tid[v])
            return ret;
        if(dep[u] > dep[v])
            swap(u, v);
        ret = max(ret, st.Query(tid[u] + 1, tid[v]));
        return ret;
    }
    
    struct DisjointSetUnion {
        static const int MAXN = 200000;
        int n, fa[MAXN + 5], rnk[MAXN + 5];
    
        void Init(int _n) {
            n = _n;
            for(int i = 1; i <= n; i++) {
                fa[i] = i;
                rnk[i] = 1;
            }
        }
    
        int Find(int u) {
            int r = fa[u];
            while(fa[r] != r)
                r = fa[r];
            int t;
            while(fa[u] != r) {
                t = fa[u];
                fa[u] = r;
                u = t;
            }
            return r;
        }
    
        bool Merge(int u, int v) {
            u = Find(u), v = Find(v);
            if(u == v)
                return false;
            else {
                if(rnk[u] < rnk[v])
                    swap(u, v);
                fa[v] = u;
                rnk[u] += rnk[v];
                return true;
            }
        }
    } dsu;
    
    struct E2 {
        int w, c, u, v, i;
        bool operator<(const E2 &e)const {
            return w < e.w;
        }
    } e2[200005];
    
    int ANSI[200005], ANSW[200005];
    
    void test_case() {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; ++i)
            scanf("%d", &e2[i].w);
        for(int i = 1; i <= m; ++i)
            scanf("%d", &e2[i].c);
        for(int i = 1; i <= m; ++i) {
            scanf("%d%d", &e2[i].u, &e2[i].v);
            e2[i].i = i;
        }
        //建立原价最小生成树
        sort(e2 + 1, e2 + 1 + m);
        dsu.Init(n);
        init(n);
        int cnt = 0;
        ll sum = 0;
        for(int i = 1; i <= m; ++i) {
            if(dsu.Merge(e2[i].u, e2[i].v)) {
                addedge(e2[i].u, e2[i].v, e2[i].w, e2[i].i);
                sum += e2[i].w;
                ANSI[++cnt] = e2[i].i;
                ANSW[cnt] = e2[i].w;
                if(cnt == n - 1)
                    break;
            }
        }
    
        //对最小生成树进行树剖
        init1();
        int r = 1;
        dfs1(r, -1, {-INF, -INF});
        init2();
        dfs2(r, r);
        st.Init1();
        st.Init2(n);
    
        //统计答案
        ll ans = sum;
        int S, eid = -1, aid = -1;
        scanf("%d", &S);
        for(int j = 1; j <= m; ++j) {
            pii ret = CPTQuery(e2[j].u, e2[j].v);
            ll tmp = sum - ret.first + e2[j].w - S / e2[j].c;
            if(tmp < ans) {
                ans = tmp;
                eid = ret.second;
                aid = j;
            }
        }
        printf("%lld
    ", ans);
        for(int j = 1; j <= n - 1; ++j) {
            if(ANSI[j] != eid)
                printf("%d %d
    ", ANSI[j], ANSW[j]);
            else
                printf("%d %d
    ", e2[aid].i, e2[aid].w - S / e2[aid].c);
        }
        return;
    }
    
  • 相关阅读:
    JAVA后端方面,如何快速达到能实习的程度
    如何高效地把Spring boot学到能干活的程度
    零高并发项目经验的人如何通过面试得到实践机会?
    Java学到什么程度可以面试工作?
    Java培训班学员如何找工作?如何过试用期?
    作为Java技术面试官,我如何深挖候选人的技能
    今年我拿到了期望中的收入,同时更希望能在睡后收入上有进一步的发展——2021年我的总结与思考
    程序员月薪一万到底难不难?
    自学java,如何快速地找到工作
    搞IT的应届生如何写好简历?
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12274986.html
Copyright © 2011-2022 走看看