跳转至

CF2038

CF2038A Bonus Project

\(n\) 个人合作完成一个任务,这个任务需要 \(k\) 的工作量,完成任务后,第 \(i\) 个人会收到 \(a_i\) 的报酬,而完成一个单位的工作量会产生 \(b_i\) 的代价。若一个人的报酬小于代价,那么他绝对不会工作。

\(n\) 个人依次决定自己的工作量,每个人都会最大化自己的收益。如果总工作量小于 \(k\),那么所有人都拿不到报酬。

问每个人会工作多少。

只要能少工作就少工作,贪心即可。

CF2038B Make It Equal

给你一个长为 \(n\) 的序列 \(a_{1\sim n}\)。你可以进行以下操作任意次:

  • 选择一个 \(i\)a[i] -= 2, a[i % n + 1]++;

问能否使序列的各个项最终非负且相等。若能,输出最小步数。

\(n\le 2\times 10^5,~a_i\le 10^9\)

设最后序列的所有值等于 \(p\)。由于 \(p+1\) 可以转移到 \(p\),并且操作是无序的,因此 \(p\) 具有单调性。考虑二分答案。

如果一个 \(a_i>p\),那么至少要给它减少到 \(p\)。如果奇偶性不同,那么减少到 \(p-1\)。暴力从 \(1\) 做到 \(n\),如果不合法就再转一轮,直到彻底不合法或者合法停止。发现这样做的复杂度其实是对的,因为一个 \(a_i\) 最多贡献到后 \(30\) 项。

CF2038C DIY

给定一个序列 \(a_{1\sim n}\)。你需要从中选出下标互不相同的八项 \(a,b,c,d,e,f,g,h\),满足 \((a,b),(c,d),(e,f),(g,h)\) 构成一个四边平行于坐标轴的长方形。构造一种方案,使得长方形面积最大,或者报告无解。

\(n\le 2\times 10^5\)

md 原题翻译错了,没写平行于坐标轴,让我对着一个真黄题想了 \(20\) 分钟,服了。

注意到 \(8\) 个数可以分为 \(4\) 对相等的数字。因此记录出现至少两次的数字,然后选最大次大和最小次小作为 \(8\) 个数即可。

CF2038D Divide OR Conquer

给定一个长为 \(n\) 的序列 \(a_{1\sim n}\),你需要将序列划分为任意段,满足每段的按位或单调不降。问划分方案数对 \(998244353\) 取模的结果。

\(n\le 2\times 10^5\)

注意到,固定右端点后,不同的区间按位或只有 \(\log n\) 种。因此怎么做都行。可以先二分出每种按位或结果对应的左端点区间,然后把当前下标挂在区间两端,采用刷表法&树状数组进行转移即可。

然而这种做法太难写了,而且不知道为什么卡常。因此这里介绍另一种 dp 转移方法。先设 \(f_{i,j}\) 表示前 \(i\) 个数字划分成了若干段,最后一段为 \(j\) 的方案数。考虑转移,考察前缀内的最后一段区间 \([k,i]\),对于 \([k,i-1]\) 是合法区间的情况,我们从 \(f_{i-1,j'}\) 直接转移到 \(f_{i,j'\oplus a_i}\)

【先咕了,等会】

CF2038E Barrels

\(v_i\) 表示桶的水面高度,\(l_i\) 表示桶左边的管道高度,\(r_i\) 表示桶右边的管道高度。

容易想到一种贪心策略:从最右边的桶开始,一直扔粘土直到堵住,然后下一个桶继续扔到堵住,直到最左边。发现 hack 不掉,应该是对的。

考虑怎么维护。扔粘土可以看作倒水。然后有一些桶的水面会通过管道连在一起,而无论什么操作都不会发生连通块的分裂,因此可以用并查集维护。每次加水先线段树二分找到最靠右的 \(v<l\) 的一个连通块,然后加水直到 \(v=r\)(找下一个)或者 \(v=l\)(合并连通块)或者本次的水用完了(炸掉最右边的桶)。总操作次数应该是 \(O(n)\) 的。

CF2038F Alternative Platforms

给定两个长为 \(n\) 的序列 \(a_{1\sim n},b_{1\sim n}\),对每个 \(1\le k\le n\) 求出

\[ \sum_{S\subseteq \{1,2,\cdots,n\}\wedge |S|=k}\max(\min_{i\in S}a_i,\min_{i\in S}b_i) \]

\(n\le 2\times 10^5,\ a_i\le 10^6\)

一个 \(\max\) 套两个 \(\min\) 不好处理,分讨二者大小也没什么前途。考虑经典转化:\(\max(a,b)=a+b-\min(a,b)\)。因此问题转化为:

\[ \sum_{S\subseteq \{1,2,\cdots,n\}}\min{a_i}+\sum_{S\subseteq \{1,2,\cdots,n\}}\min{b_i}-\sum_{S\subseteq \{1,2,\cdots,n\}}\min\bigl\{\min(a_i,b_i)\big\} \]

三项分别对应三个独立的序列。考虑解决一个序列的问题:

\[ \sum_{S\subseteq \{1,2,\cdots,n\}\wedge |S|=k}\min{a_i} \]

序列显然无序,因此先对其进行升序排序,答案即:

\[ ans_k=\sum_{i=1}^{n}a_i\binom{n-i}{k-1} \]

题解说如果足够熟练应该一眼看出来是卷积。但是我不行,所以来推式子。

\[ \begin{align*} \sum_{i=1}^{n}a_i\binom{n-i}{k-1}&=\sum_{i=1}^{n}\frac{(n-i)!a_i}{(n-i-k+1)!(k-1)!}\\ &=\frac{1}{(k-1)!}\sum_{i=1}^{n}\frac{(n-i)!a_i}{(n-k+1-i)!} \end{align*} \]

是不是能看出来了:

\[ ans_k=\frac{1}{(k-1)!}[x^{n-k+1}]\Bigl(\bigl(\sum_{i=1}^{n}(n-i)!a_ix^i\big)\times \bigl(\sum_{i=0}^{n}\frac{1}{i!}\big)\Big) \]

时间复杂度 \(O(n\log n)\)

代码
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
const int N = 6e5 + 10;
const int MOD = 998244353;

int n;
int a[N], b[N], c[N];
int res[N], ans[N];

int inv[N], fact[N], ifact[N];
int d[N];

inline int qpow(int a, int b) {
    int res = 1;
    while(b) {
        if(b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

void ntt(int a[], int n, int g) {
    static int inv[N];
    for(int i = 0; i < n; i++) inv[i] = (inv[i >> 1] >> 1) | ((n >> 1) * (i & 1));
    for(int i = 0; i < n; i++) if(i < inv[i]) swap(a[i], a[inv[i]]);
    for(int k = 1; k < n; k <<= 1) {
        int w0 = qpow(g, (MOD - 1) / (2 * k));
        for(int i = 0; i < n; i += 2 * k) {
            int w = 1;
            for(int j = 0; j < k; j++) {
                int x = a[i + j], y = a[i + j + k] * w % MOD;
                a[i + j] = (x + y) % MOD;
                a[i + j + k] = (x + MOD - y) % MOD;
                w = w * w0 % MOD;
            }
        }
    }
}

void solve(int a[]) {
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; i++) a[i] = a[i] * fact[n - i] % MOD;
    ntt(a, 524288, 3);
    for(int i = 0; i < 524288; i++) res[i] = a[i] * d[i] % MOD;
    ntt(res, 524288, 332748118);
    for(int i = 1; i <= n; i++) res[i] = res[i] * inv[524288] % MOD;
    for(int i = 1; i < n - i + 1; i++) swap(res[i], res[n - i + 1]);
    for(int i = 1; i <= n; i++) res[i] = res[i] * ifact[i - 1] % MOD;
}

signed main() {

    ios::sync_with_stdio(0);
    cin.tie(0);

    inv[1] = fact[0] = ifact[0] = 1;
    for(int i = 2; i < N; i++) inv[i] = MOD - inv[MOD % i] * (MOD / i) % MOD;
    for(int i = 1; i < N; i++) fact[i] = fact[i - 1] * i % MOD;
    for(int i = 1; i < N; i++) ifact[i] = ifact[i - 1] * inv[i] % MOD;

    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) cin >> b[i];
    for(int i = 1; i <= n; i++) c[i] = min(a[i], b[i]);

    for(int i = 0; i < 262144; i++) d[i] = ifact[i];
    ntt(d, 524288, 3);

    solve(a);
    for(int i = 1; i <= n; i++) ans[i] = res[i];
    solve(b);
    for(int i = 1; i <= n; i++) ans[i] = (ans[i] + res[i]) % MOD;
    solve(c);
    for(int i = 1; i <= n; i++) ans[i] = (ans[i] + MOD - res[i]) % MOD;

    for(int i = 1; i <= n; i++) cout << ans[i] * (ifact[n] * fact[n - i] % MOD * fact[i] % MOD) % MOD << ' ';
    cout << '\n';

    return 0;
}

CF2038G Guess One Character

容易先想到询问 1 的数量,然后想知道连续段数量。发现可以询问 01 或者 10,但是在开头结尾等于 \(1\) 的情况下会少统计一个连续段。又发现询问 11 可以直接知道正确的连续段数。然后不难知道开头或结尾是什么字符。

CF2038H Galactic Council

转化之后是费用流板子。先咕了。

CF2038I Polyathlon

二分哈希排序。先咕了。