竞赛讨论区 > 提高组第六场 题解
头像
C.C.A
发布于 2022-10-16 14:02 湖南
+ 关注

提高组第六场 题解

A

观察数据生成器可以发现对于每次操作均有

考虑将这些三元组想象成空间直角坐标系中以 为对顶点的长方体。

将操作分成两部分,一部分是对 轴同时操作的,可以一开始就用前缀 统计完,将长方体变成一个底面为阶梯状的直棱柱。另一部分是对 操作的,考虑从小到大枚举 ,然后将 的操作变成一条横线或一条竖线,容易发现这两条线也是具有单调性的,于是每层留下的点数可以直接计算。

最后用总点数减去剩下的点数即为删除的三元组数量,由于答案最大可能到 ,所以需要使用高精或 __int128。总时间复杂度为

#include <bits/stdc++.h>
using namespace std;

const int N = 3e7 + 10;
typedef unsigned long long ull;

int n, A, B, C, u[N], v[N], w[N];

ull Rand (ull &k1, ull &k2) {
    ull k3 = k1, k4 = k2;
    k1 = k4;
    k3 ^= (k3 << 23);
    k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
    return k2 + k4;
}

void GetData () {
    ull x, y;
    cin >> n >> A >> B >> C >> x >> y;
    for (int i = 1; i <= n; i++) {
        u[i] = Rand(x, y) % A + 1;
        v[i] = Rand(x, y) % B + 1;
        w[i] = Rand(x, y) % C + 1;
        if (Rand(x, y) % 3 == 0) u[i] = A;
        if (Rand(x, y) % 3 == 0) v[i] = B;
        if ((u[i] != A) && (v[i] != B)) w[i] = C;
    }
}

void Write (__int128 x) {
    if (x < 10) cout << (int)x;
    else Write(x / 10), cout << (int)(x % 10);
}

int h[N], l[N], Mx[N], My[N];

int main () {

    GetData();

    for (int i = 1; i <= n; i++) {
        if (w[i] == C) h[u[i]] = max(h[u[i]], v[i]);
        else if (u[i] == A) My[w[i]] = max(My[w[i]], v[i]);
        else if (v[i] == B) Mx[w[i]] = max(Mx[w[i]], u[i]);
    }

    for (int i = A; i >= 1; i--) h[i] = max(h[i], h[i + 1]);
    for (int i = C; i >= 1; i--) Mx[i] = max(Mx[i], Mx[i + 1]);
    for (int i = C; i >= 1; i--) My[i] = max(My[i], My[i + 1]);

    for (int i = 1, now = A; i <= B; i++) {
        while (now && h[now] < i) now--;
        l[i] = now;
    }

    __int128 ans = 0, sum = 0, All;
    for (int i = 1, Lx = A, Ly = B; i <= C; i++) {
        while (Lx > Mx[i]) sum += B - Ly - max(0, h[Lx] - Ly), Lx--;
        while (Ly > My[i]) sum += A - Lx - max(0, l[Ly] - Lx), Ly--;
        ans += sum;
    }
    All = A, All *= B, All *= C;
    Write(All - ans);

    return 0;
}

B

不难发现,如果我们知道最后剩下的物品 ,那质量优于 和劣于 的物品产生的贡献都仅与当前剩余物品的数量有关。

于是考虑枚举 ,记 表示前 道工序剩下 个物品优于 个物品劣于 的概率,可以转移到 ,转移系数仅与数量有关, 即为剩下物品为 的概率。

容易发现再确定 的值是确定的,故状态仅需记为

时间复杂度

#include <bits/stdc++.h>
using namespace std;

const int N = 305, mod = 1e9 + 7;
int n, p[N], f[N][N], pr1[N][N], pr2[N][N], ans;

int main () {

    cin >> n;
    for (int i = 1; i < n; i++) cin >> p[i];

    for (int i = 1; i < n; i++) {
        pr1[i][0] = 1;
        for (int j = 1; j <= n - i + 1; j++) {
            if (j < n - i + 1) pr2[i][j] = 1ll * pr1[i][j - 1] * p[i] % mod;
            else pr2[i][j] = pr1[i][j - 1];
            pr1[i][j] = 1ll * pr1[i][j - 1] * (mod + 1 - p[i]) % mod;
        }
        for (int j = 2; j <= n - i + 1; j++)
            pr2[i][j] = (pr2[i][j] + pr2[i][j - 1]) % mod;
    }

    for (int i = 1; i <= n; i++) {
        memset (f, 0, sizeof f), f[1][i] = 1;
        for (int j = 1; j < n; j++)
            for (int k = 1; k <= i; k++) if (f[j][k]) {
                f[j + 1][k - 1] = (f[j + 1][k - 1] + 1ll * f[j][k] * pr2[j][k - 1] % mod) % mod;
                f[j + 1][k] = (f[j + 1][k] + 1ll * f[j][k] * (mod + 1 - pr2[j][k]) % mod) % mod;
            }
        ans = (ans + 1ll * i * f[n][1]) % mod;
    }
    cout << ans << endl;

    return 0;
}

C

观察到 是具有单调性的,如果一个数字 导致一个区间不可行,那么这个区间所有包含 的子区间必然也不可行。

考虑分治, 表示当前分治到 ,那么我们把 中所有出现次数小于 的数字全部删掉,区间就变成了若干段,分别递归处理即可。如果当前区间没有删掉任何数字,那么说明当前区间就是合法的,直接更新答案。

考虑这个做法的复杂度,即考虑递归层数。不难发现当删除数字的时候递归层数才会增加,而最多有 种不同的出现次数,因此这个做法的复杂度为

考虑优化上面的做法。首先一次找出所有出现次数较小的数字比较耗时,我们可以找到任意一个出现次数不满足要求的数字,并且从这个数字处把区间分成两半进行递归,并不会影响正确性,还大大降低了编程复杂度。

但是由于分治两边可能不均等,会导致时间复杂度退化成 ,但如果我们在 的时间内完成当前层的操作,复杂度就是启发式合并的复杂度,即

于是利用类似于 meet in the middle 的思想,我们从区间两端同时找出现次数不满足要求的数字,显然寻找的时间就是 了。但是我们还需要维护每个数字的出现次数,考虑记 数组为每个数字的出现次数,并且假定每次递归时 数组恰好是当前区间中所有数字的出现次数,并且返回时清空 数组。

把拆开的两区间中较大的称为大区间,较小的称为小区间,每次找到分割点后,先把小区间中所有数字从 当中去掉,就可以直接递归大区间了。之后 数组被清空,我们只需要再遍历一遍小区间,把所有数字加到 中,递归完毕后 也恰好被清空,符合返回要求。

特殊的,如果一个区间找不到分割点,则需要遍历整个区间清空 ,复杂度显然没有问题。初始时, 必须设置为每个数字的出现次数再进行递归。

由于每一步花费的复杂度都是 ,因此总复杂度为

#include <bits/stdc++.h>
using namespace std;

const int N = 1e6 + 10;

int n, cnt[N], b[N], a[N], ans;

void calc (int l, int r) {
    if (r < l || r - l + 1 <= ans) {
        for (int i = l; i <= r; i++) --cnt[a[i]];
        return;
    }
    if (l == r) {
        --cnt[a[l]];
        if (b[1] == 1) ans = max(ans, 1);
        return;
    }
    int *p = a + l, *q = a + r, t = -1, val = b[r - l + 1];
    while (p <= q) {
        if (cnt[*p] < val) { t = p - a; break; }
        if (cnt[*q] < val) { t = q - a; break; }
        ++p, --q;
    }
    if (t < 0) {
        ans = max(ans, r - l + 1);
        for (int i = l; i <= r; i++) --cnt[a[i]];
        return;
    }
    if (t <= (l + r) >> 1) {
        for (int i = l; i <= t; i++) --cnt[a[i]];
        calc(t + 1, r);
        for (int i = l; i < t; i++) ++cnt[a[i]];
        calc(l, t - 1);
    } else {
        for (int i = t; i <= r; i++) --cnt[a[i]];
        calc(l, t - 1);
        for (int i = t + 1; i <= r; i++) ++cnt[a[i]];
        calc(t + 1, r);
    }
}

int main() {

    cin >> n;
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
    for (int i = 1; i <= n; i++) ++cnt[a[i]];

    calc(1, n), printf("%d\n", ans);

    return 0;
}

D

首先进行一步转化,极长连续段的平方等于满足 的数对 的数量。期望即为所有 ,满足 的概率和。

  • 中有两个不同的颜色时,

  • 中有且仅有一种确定的颜色时,

  • 中没有确定的颜色时,

于是只需要求 。我们发现这个信息时容易合并的,只需要维护:该区间内概率的和,左边第一个确定的颜色,右边第一个确定的颜***间长度,区间中未确定的数的数量, 中有且仅有一种确定的颜色时/没有确定的颜色时 的和。

时间复杂度 ,常数很大,实现细节见代码。

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10, mod = 998244353;

int Pow (int a, int k) {
    int res = 1;
    for (; k; k >>= 1, a = 1ll * a * a % mod)
        if (k & 1) res = 1ll * res * a % mod;
    return res;
}

int n, c, q, a[N], T[3][N], f[N], inv;

struct Seg {
    int l, r, lc, rc, lans, rans, tans, all, tag;
} seg[N << 3], ans;

bool flag;

Seg Merge (Seg a, Seg b) {
    Seg res;
    res.l = (!a.lc) ? a.l + b.l : a.l;
    res.r = (!b.rc) ? a.r + b.r : b.r;
    res.lc = (!a.lc) ? b.lc : a.lc;
    res.rc = (!b.rc) ? a.rc : b.rc;
    (a.lc) ? res.lans = a.lans : res.lans = 0;
    if (a.tag) {
        if (a.lc) res.lans = (res.lans + (T[1][a.all + b.l] - T[1][a.all] + mod) % mod) % mod;
        if (a.lc && b.lc && a.lc != b.lc); 
        else res.lans = (res.lans + 1ll * T[0][a.all] * b.lans % mod) % mod;
    }
    (b.rc) ? res.rans = b.rans : res.rans = 0;
    if (b.tag) {
        if (b.rc) res.rans = (res.rans + (T[1][b.all + a.r] - T[1][b.all] + mod) % mod) % mod;
        if (a.rc && b.rc && a.rc != b.rc); 
        else res.rans = (res.rans + 1ll * T[0][b.all] * a.rans % mod) % mod;
    }
    res.tans = (a.tans + b.tans) % mod;
    int x = T[1][a.r], y = T[1][b.l];
    res.tans = (res.tans + 1ll * T[2][a.r] * y % mod) % mod;
    if (!a.rc || !b.lc || a.rc == b.lc)
        res.tans = (res.tans + (1ll * ((a.rans + x) % mod) * ((b.lans + y) % mod) % mod - 1ll * x * y % mod + mod) % mod) % mod;
    else res.tans = (res.tans + 1ll * x * b.lans % mod) % mod, res.tans = (res.tans + 1ll * y * a.rans % mod) % mod;
    res.tag = a.tag & b.tag & (!a.lc || !b.lc || a.lc == b.lc);
    res.all = a.all + b.all;
    return res;
}

void Build (int p, int l, int r) {
    if (l == r) {
        if (a[l]) seg[p] = (Seg){0, 0, a[l], a[l], 1, 1, 1, 0, 1};
        else seg[p] = (Seg){1, 1, 0, 0, 0, 0, 1, 1, 1};
        return;
    }
    Build (p << 1, l, (l + r) >> 1);
    Build (p << 1 | 1, ((l + r) >> 1) + 1, r);
    seg[p] = Merge(seg[p << 1], seg[p << 1 | 1]);
}

void Update (int p, int l, int r, int k) {
    if (l == r) {
        if (a[l]) seg[p] = (Seg){0, 0, a[l], a[l], 1, 1, 1, 0, 1};
        else seg[p] = (Seg){1, 1, 0, 0, 0, 0, 1, 1, 1};
        return;
    }
    if((l + r) >> 1 >= k) Update(p << 1, l, (l + r) >> 1, k);
    else Update(p << 1 | 1, ((l + r) >> 1) + 1, r, k);
    seg[p] = Merge(seg[p << 1], seg[p << 1 | 1]);
}

void Query (int p, int l, int r, int kl, int kr) {
    if (kl > r || kr < l) return;
    if (kl <= l && kr >= r) {
        if (!flag) flag = 1, ans = seg[p];
        else ans = Merge(ans, seg[p]);
        return;
    }
    Query (p << 1, l, (l + r) >> 1, kl, kr);
    Query (p << 1 | 1, ((l + r) >> 1) + 1, r, kl, kr);
}

int main() {

    scanf("%d%d%d", &n, &c, &q);
    inv = Pow(c, mod - 2);

    for (int i = 0; i < 2; i++) {
        T[i][0] = 1;
        int x = i ? Pow(c, mod - 2) : c;
        for (int j = 1; j <= n; j++) T[i][j] = 1ll * T[i][j - 1] * x % mod;
        T[i][0] = 0;
        for (int j = 2; j <= n; j++) T[i][j] = (T[i][j - 1] + T[i][j]) % mod;
    }
    T[0][0] = 1;
    for (int j = 1; j <= n; j++) T[0][j] = 1ll * T[0][j - 1] * inv % mod;
    for (int j = 0; j <= n; j++) T[2][j] = 1ll * T[1][j] * c % mod;

    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    Build(1, 1, n);
    for (int i = 1, op, tx, ty; i <= q; i++) {
        scanf("%d%d%d", &op, &tx, &ty);
        if (op == 1) a[tx] = ty, Update(1, 1, n, tx);
        else flag = 0, Query(1, 1, n, tx, ty), printf("%d\n", (1ll * ans.tans * 2 % mod - (ty - tx + 1) + mod) % mod);
    }

    return 0;
}

全部评论

(3) 回帖
加载中...
话题 回帖

等你来战

查看全部

热门推荐