Codeforces Round #343 (Div. 2)

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

说起来经过一个月分数不升反降,道理很简单,前面起飞的是数据结构和数论场,假如连续整点图论就一直降落了。没事多积累一下知道在进步。

A - Far Relative’s Birthday Cake

水题,随便组合一下。

B - Far Relative’s Problem

水题,不过题意有点混乱,搞点差分就行。

*C - Famil Door and Brackets

题意:给一个长度为 \(m(1\leq m \leq n)\) 的括号串 \(s\) ,在它前面和后面加上可以为空的前缀 \(p\) 和后缀 \(q\) ,使其结果为恰好为长度为 \(n(1\leq n \leq 2\cdot 10^5)\) 的合法括号串。限制 \(n-m\leq2000\)

题解:由于这个限制,所以可以用个 \(O(n^2)\) 的算法。先统计出整个 \(s\) 串的左右差和最小左右差。然后就可以设计一些转移。

不过越界真的好烦耶!

\(dp1[i][j]\) 为前缀放了i个,左右差为j的方案数,由必须合法可知j>=0。

\(dp2[i][j]\) 为前缀和后缀共放了i个,左右差为j的方案数,由必须合法可知j>=0。

由必须合法可知,从前缀加上 \(s\) 后要求结果仍>=0,故必须从 \(j\geq minsum\) 的前缀,转移到 \(j+sum\) 的后缀,且这里不能够越界!

int n, m;
char s[100005];
ll dp1[2005][2005];
ll dp2[2005][2005];

void test_case() {
    int n, m;
    scanf("%d%d%s", &n, &m, s + 1);
    int sum = 0, minsum = 0;
    for(int i = 1; i <= m; ++i) {
        if(s[i] == '(')
            ++sum;
        else {
            --sum;
            minsum = min(minsum, sum);
        }
    }
    dp1[0][0] = 1;
    int c = 2000;
    for(int i = 1; i <= c; ++i) {
        for(int j = 0; j <= c; ++j) {
            dp1[i][j] += (j > 0 ? dp1[i - 1][j - 1] : 0) + dp1[i - 1][j + 1];
            if(dp1[i][j] >= MOD)
                dp1[i][j] %= MOD;
        }
    }

//    for(int i = 0; i <= c; ++i) {
//        for(int j = 0; j <= c; ++j)
//            printf("dp1[%d][%d]=%lld\n", i, j, dp1[i][j]);
//        puts("");
//    }
//    puts("---");

    for(int j = -minsum; j + sum <= c && j <= c; ++j) {
        for(int k = 0; k <= c; ++k)
            dp2[k][j + sum] += dp1[k][j];
    }

//    for(int i = 0; i <= c; ++i) {
//        for(int j = 0; j <= c; ++j)
//            printf("dp2[%d][%d]=%lld\n", i, j, dp2[i][j]);
//        puts("");
//    }
//    puts("---");

    for(int i = 1; i <= c; ++i) {
        for(int j = 0; j <= c; ++j) {
            dp2[i][j] += (j > 0 ? dp2[i - 1][j - 1] : 0) + dp2[i - 1][j + 1];
            if(dp2[i][j] >= MOD)
                dp2[i][j] %= MOD;
        }
    }

//    for(int i = 0; i <= c; ++i) {
//        for(int j = 0; j <= c; ++j)
//            printf("dp2[%d][%d]=%lld\n", i, j, dp2[i][j]);
//        puts("");
//    }
//    puts("---");

    printf("%lld\n", dp2[n - m][0] % MOD);
}

*D - Babaei and Birthday Cake

粗看像单调队列优化dp什么的,仔细分析。

题意:有 \(n(1\leq n\leq 10^5)\) 个圆柱体蛋糕,其中第 \(i\) 个蛋糕要么放在桌子上,要么放在一个 \(j\(V_j 的蛋糕上。求最大体积的蛋糕。

题解:意思是什么面积和高都是没用的,全部算体积。看起来也只和最后一个蛋糕有关。

\(dp[i]\) 表示以第 \(i\) 个蛋糕结尾的最大体积,则: \(dp[i]=V_i+\max\limits_{j=0\; and \; V_j ,要求小于某个key的value的最小值。这不是平衡树吗?仔细想想权值线段树也可做。

注意到其实可以把圆周率提取出来,所以是准确运算。而且相同的V值,后面的结果肯定不会比前面更差。(所以修改的时候直接赋值而不是取max)

const double eps = 1e-8;
const double PI = acos(-1.0);

struct SegmentTree {
#define ls (o<<1)
#define rs (o<<1|1)
    static const int MAXN = 100000;
    ll mx[(MAXN << 2) + 5];

    void PushUp(int o) {
        mx[o] = max(mx[ls], mx[rs]);
    }

    void Build(int o, int l, int r) {
        if(l == r)
            mx[o] = 0;
        else {
            int m = l + r >> 1;
            Build(ls, l, m);
            Build(rs, m + 1, r);
            PushUp(o);
        }
    }

    void Update(int o, int l, int r, int p, ll v) {
        if(l == r) {
            mx[o] = v;
            return;
        } else {
            int m = l + r >> 1;
            if(p <= m)
                Update(ls, l, m, p, v);
            if(p >= m + 1)
                Update(rs, m + 1, r, p, v);
            PushUp(o);
        }
    }

    ll Query(int o, int l, int r, int ql, int qr) {
        if(ql <= l && r <= qr) {
            return mx[o];
        } else {
            int m = l + r >> 1;
            ll res = 0;
            if(ql <= m)
                res = Query(ls, l, m, ql, qr);
            if(qr >= m + 1)
                res = max(res, Query(rs, m + 1, r, ql, qr));
            return res;
        }
    }
#undef ls
#undef rs
} st;

int n;
ll V[100005];
ll v[100005];
int id[100005];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        ll r, h;
        scanf("%lld%lld", &r, &h);
        V[i] = v[i] = r * r * h;
    }
    sort(v + 1, v + 1 + n);
    int c = unique(v + 1, v + 1 + n) - (v + 1);
    for(int i = 1; i <= n; ++i)
        id[i] = lower_bound(v + 1, v + 1 + c, V[i]) - v;
    st.Build(1, 1, c);
    for(int i = 1; i <= n; ++i) {
        if(id[i] == 1) {
            st.Update(1, 1, c, 1, V[i]);
            continue;
        } else {
            ll res = st.Query(1, 1, c, 1, id[i] - 1);
            st.Update(1, 1, c, id[i], V[i] + res);
            continue;
        }
    }
    printf("%.12f\n", PI * st.Query(1, 1, c, 1, c));
}

收获:检查线段树的下标,不要写错线段树的下标!

你可能感兴趣的:(Codeforces Round #343 (Div. 2))