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;
}
原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12274986.html