W
H
X

Codeforces Round #626 (Div. 1)

Codeforces Round #626 (Div. 1)

A

B

对每一位分别计算,只考虑 \(x,y\) 的前 \(k\) 位,若 \(x+y\) 的第 \(k\) 位为 \(1\),则 \(x+y\in[2^k,2^{k+1})\cup[2^k+2^{k+1},2^{k+2})\),排序后双指针统计即可

C

结论题,但大多数题解的证明有问题,建议看官方题解或[中文版题解](题解 CF1322C [Instant Noodles] - 老年退役选手 QwQcOrZ 的小窝 - 洛谷博客 (luogu.com.cn)),享受优美严谨的证明

D

\(f_{i,j}\) 表示有 \(j\) 个等级为 \(i\) 的选手的最大收益(当前最大等级为 \(i\))。加入一个等级 \(l_i\) 的人有两种转移:1. 推一遍 \(f_{l_i,*}\);2. 把 \(l_i\) 推向 \(l_{i+1},l_{i+2}\dots\)。在这样的转移中,如果后加入的 \(i<j,l_j<l_i\),则 \(i\) 不会影响 \(j\),正好与题目要求相反,所以倒着处理每个人。

转移 \(1\) 的时间复杂度显然是 \(O(n^2)\),对于转移 \(2\),每向上一层数量减半,复杂度为 \(O(n\times(n+\frac{n}{2}+\frac{n}{4}+\dots))=O(n^2)\)

int n, m, mx, res, l[N], s[N], c[N << 1], f[N << 1][N << 1];
void Max (int &a, const int b) { if (b > a) a = b; }
void modify (int p, int x) {
    int tmp = 0; while (f[p][tmp] != INT_MIN) ++tmp;
    for (int i = tmp; i >= 1; --i) Max (f[p][i], f[p][i - 1] + c[p] - x);
}
void update (int p) {
    for (int i = 0; i <= n && f[p][i] != INT_MIN; ++i)
        Max (f[p + 1][i >> 1], f[p][i] + (i >> 1) * c[p + 1]);
    if (p < n + m) update (p + 1);
}
signed main() {
    read (n), read (m);
    for (int i = 1; i <= n; ++i) read (l[i]);
    for (int i = 1; i <= n; ++i) read (s[i]);
    for (int i = 1; i <= n + m; ++i)
        for (int j = 0; j <= n * 2; ++j) f[i][j] = INT_MIN;
    for (int i = 1; i <= n + m; ++i) read (c[i]), f[i][0] = 0;
    for (int i = n; i >= 1; --i) modify (l[i], s[i]), update (l[i]);
    for (int i = 1; i <= n + m + 1; ++i) Max (res, f[i][1]);
    return printf ("%lld\n", res), 0;
}

E

暴力做法如何确定最后的序列?枚举一个值 \(k\)\(b_i=(a_i<k)\),也就是只记录 \(a_i\)\(k\) 的大小关系。设 \(a',b'\) 为变换后最终得到的 \(a,b\)。取中位数的操作并不会改变这个大小关系。不难发现,一段长度 \(\ge2\) 的连续的 \(0/1\) 段永远也不会变(称它们为 \(0/1\) 段),相邻的连续段之间夹了 \(010101\dots\) 这样的东西,然后会从两头往中间扩散,这样就得到了 \(b'\),算完所有 \(k\) 后也就可以得到 \(a'\),而操作次数就是所有 \(k\) 对应的 \(b\) 的最长的 \(01\) 段长度除以 \(2\)\(k\) 取所有 \(a_i\),复杂度 \(O(n^2)\)

介绍两种优化方法。

第一种:依旧从小到大枚举 \(k=a_i\),此时 \(b\) 中有一些 \(0\) 变为 \(1\),对于每一个变化的位置,\(b’\) 中也有一些 \(0\) 变为 \(1\),且构成一个区间,可用 \(set\) 等数据结构维护,\(O(n \log n)\)。但又长又慢

第二种:对单个位置 \(x\) 进行计算,因为只考虑一个数,所以可以二分 \(k\),不必枚举。剩下的问题是如何找到离 \(x\) 最近的连续段。令 \(p_i=min/max(a_i,a_{i+1})\),然后反着处理 \(p\) 的最大值最小值的 \(ST\) 表(对于 \(min(a_i,a_{i+1})\) 反过来处理 \(max\)\(st\)\(MX\),对于 \(max(a_i,a_{i+1})\) 处理 \(min\)\(st\)\(MN\)),再套个二分分别算出最近的 \(0\) 段和 \(1\) 段,即可得到最近的连续段,\(O(n \log^2 n)\)。但还有更强的做法。不用二分 \(k\),直接二分距离 \(d\)。设 \(A=MN.ask(i-d-1,i+d),B=MX.ask(i-d-1,i+d)\)。取 \(k=min(A,B)\),此时 \(B\ge k\),说明在距离 \(d\) 内已经碰到 \(0\) 段,\(A\ge k\),说明距离 \(d\) 内没有 \(1\) 段,那么先碰到 \(0\) 段,\(b'_i=(a'_i<k)=0\),即 \(a’_i\ge k\)。只需找到最大的 \(k=min(A,B)\) 就是 \(a'_i\) 的值。随着 \(d\) 增大,\(A\) 单减,\(B\) 单增,二分交点即可求得最大值(函数不连续,不一定有真正的交点)。至于操作次数,也可以顺带着求出

int n, res, a[N], lg[N], ma[N], na[N], b[N];
struct st {
    int c[20][N];
    void work (int *a) {
        memcpy (c[0], a, (n + 1) << 2);
        for (int j = 1; j < 20; ++j)
            for (int i = 0; i + (1 << j) - 1 <= n; ++i)
                c[j][i] = min (c[j - 1][i], c[j - 1][i + (1 << (j - 1))]);
    }
    int ask (int x, int d) {
        int l = x - d - 1, r = x + d, t = lg[r - l + 1];
        return min (c[t][l], c[t][r - (1 << t) + 1]);
    }
} mx, mn;
signed main() {
    read (n); lg[0] = -1;
    for (int i = 1; i <= n + 1; ++i) lg[i] = lg[i >> 1] + 1;
    for (int i = 1; i <= n; ++i) read (a[i]);
    a[0] = a[1], a[n + 1] = a[n];
    for (int i = 0; i <= n; ++i)
        ma[i] = -min (a[i], a[i + 1]), na[i] = max (a[i], a[i + 1]);
    mn.work (na), mx.work (ma);
    for (int i = 1; i <= n; ++i) {
        int l = 0, r = min (i - 1, n - i), mid;
        int ta, tb, val = 0;
        while (l <= r) {
            mid = l + r >> 1;
            ta = mn.ask (i, mid), tb = -mx.ask (i, mid);
            val = max (val, min (ta, tb));
            ta > tb ? l = mid + 1 : r = mid - 1;
        }
        b[i] = val, res = max (res, r + 1);
    }
    printf ("%d\n", res);
    for (int i = 1; i <= n; ++i) printf ("%d ", b[i]); puts ("");
    return 0;
}

F

。。。。。

posted @ 2021-06-21 20:44  -敲键盘的猫-  阅读(53)  评论(0编辑  收藏  举报