Loading

2024牛客多校第9场

9

B Break Sequence (B)

似乎不止一次遇到线段树优化dp了,但仍然没做出来()

\(dp[i]\) 表示到 \(i\) 位置为止、将序列分成若干段的情况总数,一个显而易见的 \(n^2\) 做法是从 \(1\)\(i - 1\) 枚举 \(j\),若 \(j + 1\)\(i\) 为合法区间,则有 \(dp[i] = \sum\limits dp[j]\). 假设当前数字 \(a[i]\) 是第 \(k\) 次出现,对于集合 \(s\) 中的某个元素 \(x \leq k\),其对应的不合法位置是 \(a[i]\)\(k - x\) 次至 \(k - x + 1\) 次出现之前的整个区间,因此可用线段树标记所有不合法区间,每次查询即寻找未被标记的 \(\sum\limits dp[j]\);维护 \(sum\) 作为当前区间标记值最小的位置权值和,即有每次标记完成后 \(dp[i] = sum[1]\). 一次操作中最多修改 \(2m\) 次标记(撤销先前标记、加上新标记),整体复杂度 \(m\cdot n\log n\).

线段树代码:

void pushup(int i) {
    cnt[i] = min(cnt[i * 2], cnt[i * 2 + 1]);
    if(cnt[i * 2] == cnt[i * 2 + 1]) {
        sum[i] = sum[i * 2] + sum[i * 2 + 1]; // 标记值相等时直接合并
        if(sum[i] >= mo) sum[i] -= mo;
    } else {
        if(cnt[i * 2] < cnt[i * 2 + 1]) sum[i] = sum[i * 2]; // 取最小值计入sum[i]中
        else sum[i] = sum[i * 2 + 1];
    }
}
// 懒标记针对cnt,pushdown中并不需要更新sum
void pushdown(int i) {
    if(lz[i]) {
        cnt[i * 2] += lz[i];
        cnt[i * 2 + 1] += lz[i];
        lz[i * 2] += lz[i];
        lz[i * 2 + 1] += lz[i];
        lz[i] = 0;
    }
}
// update单点修改sum,用于将循环中获得的dp值加入线段树中
void update(int i, int l, int r, int q, ll val) {
    if(l == r) {
        sum[i] = val;
        return;
    }
    pushdown(i);
    int mid = (l + r) / 2;
    if(q <= mid) update(i * 2, l, mid, q, val);
    else update(i * 2 + 1, mid + 1, r, q, val);
    pushup(i);
}
void modify(int i, int l, int r, int ql, int qr, int d) {
    if(ql <= l && qr >= r) {
        cnt[i] += d;
        lz[i] += d;
        return;
    }
    pushdown(i);
    int mid = (l + r) / 2;
    if(ql <= mid) modify(i * 2, l, mid, ql, qr, d);
    if(qr > mid) modify(i * 2 + 1, mid + 1, r, ql, qr, d);
    pushup(i);
}

main函数中主要代码:

    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        v[i].push_back(0); // vector保存每个数字出现的位置
    }
    for(int i = 1; i <= m; i++) {
        scanf("%d", &s[i]);
    }
    update(1, 0, n, 0, 1); // 初值
    for(int i = 1; i <= n; i++) {
        v[a[i]].push_back(i);
        int cur = v[a[i]].size() - 1;
        for(int j = 1; j <= m; j++) {
            if(cur >= s[j]) {
                modify(1, 0, n, v[a[i]][cur - s[j]], v[a[i]][cur - s[j] + 1] - 1, 1); // 不合法标记
            }
            if(cur > s[j]) {
                modify(1, 0, n, v[a[i]][cur - s[j] - 1], v[a[i]][cur - s[j]] - 1, -1); // 撤销先前的标记
            }
        }
        ll x = sum[1];
        if(i == n) printf("%lld", x);
        update(1, 0, n, i, x); // 更新dp值
    }

C Change Matrix (C)

非常巧妙的数学题,似乎默认了数学可以交给队友()导致很久没练过gcd和排列组合以外的数学题了,现在这么菜是有原因的

对于内部元素不同的矩阵,计算其行/列倍乘后的总和比较困难,若矩阵中元素完全一致则相对简单。假设 \(n\cdot m\) 的矩阵初始值为 \(1\),第 \(i\) 行倍乘系数为 \(a_i\),第 \(j\) 列倍乘系数 \(b_j\),则有:\(sum = (a_1b_1 + a_1b_2 + ... + a_1b_m) + (a_2b_1 + a_2b_2 + ...+ a_2b_m) + ... + (a_nb_1 + a_nb_2 + ... + a_nb_m) = a_1\cdot\sum\limits_{j = 1} ^ m b_j + a_2\cdot\sum\limits_{j = 1}^m b_j + ... + a_n\cdot\sum\limits_{j = 1} ^ m b_j = (\sum\limits_{i = 1} ^ n a_i)(\sum\limits_{j = 1} ^ m b_j)\);若初始值为 \(x\),则 \(sum' = sum \cdot x\);可通过 \(a,b\) 数列动态维护。

矩阵内部元素为 \(gcd(i, j)\),其分布具有规律性,如下图所示:

(图源b站牛客竞赛,雨巨讲题视频)

将整个矩阵视为多个小矩阵的叠加和,其中每个小矩阵的内部初值一致。图中颜色相同的位置含有相同的因数 \(i\),可将其当作一个小矩阵,设原矩阵中某位置元素为 \(x\),由于 \(x = \sum\limits_{\substack{i=1\\i|x}}^x \varphi(i)\)这个证明好麻烦,不想写了,手算也能发现是成立的),小矩阵初值即欧拉函数 \(\varphi(i)\). 维护 \(n\) 个小矩阵的 \(a,b\) 倍乘系数,每个矩阵的大小为 \(n / i\),对第 \(k\) 行的倍乘即对 \(k\) 的所有因数 \(i\) 改变 \(a_{k / i}\),可 \(O(1)\) 计算答案变化量;对列的倍乘同理。

筛法求欧拉函数:

vector <int> p;
bool no[N];
ll phi[N];
void ori(int n) {
    phi[1] = 1;
    for(int i = 2; i <= n; i++) {
        if(!no[i]) {
            p.push_back(i);
            phi[i] = i - 1;
        }
        for(int j = 0; j < p.size(); j++) {
            if(i * p[j] > n) break;
            no[i * p[j]] = 1;
            if(i % p[j] == 0) {
                phi[i * p[j]] = phi[i] * p[j];
                break;
            }
            phi[i * p[j]] = phi[i] * phi[p[j]];
        }
    }
}

主要代码:

    ll ans = 0;
    for(int i = 1; i <= n; i++) {
        r[i].resize(n / i + 1, 1); // r,c即题解中a,b数列
        c[i].resize(n / i + 1, 1);
        rsum[i] = csum[i] =  n / i; // 维护每个矩阵r,c之和
        ans += rsum[i] * csum[i] % mo * phi[i] % mo;
        ans %= mo;
        for(int j = 1; i * j <= n; j++) {
            fac[i * j].push_back(i); // 预处理出1-n的所有因数
        }
    }
    while(q--) {
        char ch = getchar();
        while(ch != 'R' && ch != 'C') ch = getchar();
        int x, t;
        scanf("%d%d", &x, &t);
        for(int i = 0; i < fac[x].size(); i++) {
            int j = fac[x][i];
            if(ch == 'R') {
                rsum[j] += (t - 1) * r[j][x / j] % mo;
                rsum[j] %= mo;
                ans += (t - 1) * r[j][x / j] % mo * csum[j] % mo * phi[j] % mo;
                ans %= mo;
                r[j][x / j] *= t;
                r[j][x / j] %= mo;
            } else {
                csum[j] += (t - 1) * c[j][x / j] % mo;
                csum[j] %= mo;
                ans += (t - 1) * c[j][x / j] % mo * rsum[j] % mo * phi[j] % mo;
                ans %= mo;
                c[j][x / j] *= t;
                c[j][x / j] %= mo;
            }
        }
        printf("%lld\n", ans);
    }

即使队友很擅长的领域也要自己学明白啊QwQ 像之前认真练数据结构的时候,心里想的是万一哪天被他俩踢出来我得自力更生(?)

posted @ 2024-08-17 19:24  Aderose_yr  阅读(59)  评论(0编辑  收藏  举报