Featured image of post Codeforces Round #1100(Div.1+2)

Codeforces Round #1100(Div.1+2)

B

题目大意:给定两个长度为 $n$ 的正整数数组 $a,b$ ,可以执行任意次数的以下操作:选择一个整数 $i$ 并交换 $a_i$ 和 $b_i$ ,请确认 $\max(a)+\sum\limits_{i=1}^{n}b_i$ 的最大值

数据范围: $1 \leq n \leq 10^5, 1 \leq a_i,b_i \leq 10^9$

思路:首先可以将式子化简为 $\sum\limits_{i=1}^{n}a_i+b_i-\sum\limits_{i=1}^{n-1}c_i$ ,这里 $c$ 是排序后的 $a$ 数组,从而可以发现,只需让 $a$ 的最小 $n-1$ 项最小,从而考虑到,对于 $a_i$ ,如果换能让它最小,那么必然是要换的,从而得出结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void solve() {
    int n;
    cin >> n;
    vl a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vl b(n);
    rep(i, 0, n - 1) cin >> b[i];
    ll tem = 0, tem2 = 0;
    rep(i, 0, n - 1) tem += a[i];
    rep(i, 0, n - 1) tem2 += b[i];
    ll maxx = LLONG_MIN;
    ll tem3 = 0;
    rep(i, 0, n - 1) {
        maxx = max(maxx, min(a[i], b[i]));
        tem3 += max(a[i], b[i]);
    }
    cout << tem3 + maxx << endl;
    return;
}

C1

题目大意:给定一个长度为 $n$ 的非零数组 $a$ ,最多可以执行 $n$ 次以下操作,每次操作选择一个 $a_i,a_i>0$ ,然后对于 $1 \leq j \leq i$ ,使得 $a_j=-a_j$ ,请给出使 $a$ 的和最小的操作序列。

数据范围: $2 \leq n \leq 2 \cdot 10^5, -10^9 \leq a_i \leq 10^9$

思路:首先不难想到,要使 $a$ 的和最小,那么最优情况当然是让所有的 $a$ 都是负数,又由于操作对右边的数没有影响,因此考虑从右边倒着遍历,如果碰到 $a_i>0$ 就操作即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void solve() {
    int n;
    cin >> n;
    vl a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vl res;
    int tem = 0;
    frep(i, n - 1, 0) {
        int tem2 = (a[i] < 0);
        if ((tem + tem2) % 2 == 0) {
            tem++;
            res.push_back(i + 1);
        } else
            continue;
    }
    cout << sz(res) << endl;
    rep(i, 0, sz(res) - 1) cout << res[i] << ' ';
    cout << endl;
    return;
}

C2

题目大意:跟上题相同,但是要操作得出最大和。

数据范围:跟上题相同。

思路:这题赛时卡了非常久,我能说是做完DE再来做这个的吗哈哈哈。

思路跟上题有点像,受到样例启发,发现无法将全部的数都转化为正的,同时样例的操作顺序不一定是倒序,于是想到枚举某个分割点,这个点右侧的数都不变,左侧的数全部改成正的,当然,这个点自己因为操作了会变成负的。

于是可以枚举得到分割点的位置,下面的任务就是构造一种对应的操作顺序,使得左边全部变成正的。

然后,可以枚举一下每个数需要被反转次数的奇偶性,如果它跟它相邻的不相同,显然就是这个数要被操作了,然后再次考虑怎么构造这些要被操作的数的顺序。

观察到,很多时候想执行操作,但是如果朴素地从右往左操作,可能当前的数无法被翻,因此稍微思考一下,对被操作数的数目分奇偶考虑,两两配对,再微调即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void solve() {
    int n;
    cin >> n;
    vl a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vl res;
    ll mixx = LLONG_MAX;
    vl pre(n + 1);
    vl suf(n + 2);
    rep(i, 1, n) pre[i] = pre[i - 1] + abs(a[i - 1]);
    frep(i, n, 1) suf[i] = suf[i + 1] + a[i - 1];
    ll maxx = suf[1];
    int idx = -1;
    rep(i, 1, n) {
        if (a[i - 1] > 0) {
            if (pre[i - 1] - a[i - 1] + suf[i + 1] > maxx) {
                maxx = pre[i - 1] - a[i - 1] + suf[i + 1];
                idx = i;
            }
        }
    }
    if (idx == -1) {
        cout << 0 << endl;
        cout << endl;
        return;
    }
    vl dp(n + 2);
    rep(i, 1, idx - 1) dp[i] = (a[i - 1] < 0);
    dp[idx] = 1;
    vi tem;
    rep(i, 1, n) {
        if (dp[i] != dp[i + 1]) tem.push_back(i);
    }
    if (sz(tem) % 2 == 1) {
        for (int i = 0; i <= sz(tem) - 2; i += 2) {
            res.push_back(tem[i + 1]);
            res.push_back(tem[i]);
        }
    } else {
        res.push_back(tem[0]);
        for (int i = 1; i <= sz(tem) - 2; i += 2) {
            res.push_back(tem[i + 1]);
            res.push_back(tem[i]);
        }
    }
    res.push_back(tem[sz(tem) - 1]);
    cout << sz(res) << endl;
    rep(i, 0, sz(res) - 1) cout << res[i] << ' ';
    cout << endl;
    return;
}

D

题目大意:给定两个长度为 $n$ 的数组 $a$ 跟 $b$ ,每次可以选择小于当前数组长度的 $i$ ,取出 $a_i,a_{i+1},b_i,b_{i+1}$ ,排序后得到 $s_1,s_2,s_3,s_4$ ,将 $s_2$ 填回原来 $a_i,a_{i+1}$ 的位置, $s_3$ 填回原来 $b_i,b_{i+1}$ 的位置,请求出最后 $min(a_1,b_1)$ 的最大可能值。

数据范围: $1 \leq n \leq 10^5, 1 \leq a_i,b_i \leq 2 \cdot n$

思路:看到这个形式,显然会想到是二分答案。

那么,最后两个数都要大于等于 $mid$ ,说明他们经过消除后,都能大于等于 $mid$ ,而看到邻项消除题不难想到栈,于是考虑四个数消除后的结果。

不难想到,如果四个数中至多一个数大于等于 $mid$ ,那么最后得到的 $s_2,s_3$ ,还是小于等于 $mid$ ,而如果有两个数大于等于 $mid$ ,最后得到的是 $s_3>=mid, s_2<mid$ ,而如果三个或者四个数大于等于 $mid$ ,则消除后的两个数都大于等于 $mid$ 。

于是,可以把它抽象为三种状态,邻项枚举状态的转化,而观察到,如果是两个数大于等于 $mid$ 的情况,跟其他一个情况结合,转移后还是原来的情况,所以只需要考虑两种情况,不妨记为正号跟负号,列列状态机转移即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void solve() {
    int n;
    cin >> n;
    vl a(n);
    rep(i, 0, n - 1) cin >> a[i];
    vl b(n);
    rep(i, 0, n - 1) cin >> b[i];
    auto check = [&](int mid) -> bool {
        string res = "";
        auto upd = [&](char x) {
            res.push_back(x);
            if (res == "--" || res == "+++" || res == "++-" || res == "+--")
                res.pop_back();
            else if (res == "-+")
                res.clear();
            else if (res == "+-+") {
                res.pop_back();
                res.pop_back();
            }
        };
        rep(i, 0, n - 1) {
            int tem = (a[i] >= mid) + (b[i] >= mid);
            if (tem == 1) continue;
            if (tem == 0)
                upd('-');
            else
                upd('+');
        }
        return res == "+" || res == "++";
    };
    ll l = 1, r = 2 * n, mid, ans;
    while (l <= r) {
        mid = (l + r) / 2;
        if (check(mid)) {
            ans = mid;
            l = mid + 1;
        } else
            r = mid - 1;
    }
    cout << ans << endl;
    return;
}

E

题目大意:给定一棵无根树和一个空集 $S$ ,需要对数操作 $n-1$ 次,每次操作首先从树中找出最大编号的叶子,然后把这个叶子加到 $S$ 里,如果已经在里面了就无事发生,最后从树中移除一个不是这个叶子的叶子,请求出最后得到的 $S$ 可能数目,并模 $998244353$ 。

数据范围: $2 \leq n \leq 2 \cdot 10^5$

思路:首先,不难观察到 $n-1$ 必然会作为最后被加入 $S$ 的叶子,而且它无论如何都会被加入 $S$ ,因此不妨考虑以 $n-1$ 为根进行建树。

随后,考虑一个 $u$ 被放进 $S$ ,而放进的上一个叶子是 $v$ 的情况,我们可以发现,必然有 $v<u$ ,同时,由于 $u$ 需要能被自己的子树暴露出来,因此需要有 $\text{u子树内除u的最大编号} < v < u$ (因为此处假设 $v$ 直接转移到 $u$ ,如果不满足前一个不等号,那么就不是从 $v$ 直接转移到 $u$ 了),这样就是一个预处理然后沿着编号DP的形式,同时还需要前缀和优化。

至于初始化,由于第一个被取的点是固定的,就初始化这个点的方案数是1即可。

最后,对于根,也就是 $n-1$ 来说,要让它暴露成为叶子,那么假设是从 $v$ 转移过来的,必须有 $\text{除了v所在子树的其他子树中编号最大值} < v$ ,所以需要对每个点维护一下第二大的编号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
const ll MOD = 998244353;
void solve() {
    int n, x, y;
    cin >> n;
    vvi ma(n);
    rep(i, 1, n - 1) {
        cin >> x >> y;
        ma[x - 1].push_back(y - 1);
        ma[y - 1].push_back(x - 1);
    }
    int idx = -1;
    rep(i, 0, n - 1) {
        if (sz(ma[i]) == 1) idx = max(idx, i);
    }
    if (idx == n - 1) {
        cout << 1 << endl;
        return;
    }
    vi pa(n, -1);
    pa[n - 1] = n - 1;
    queue<int> q;
    q.push(n - 1);
    vi tem;
    while (!q.empty()) {
        auto node = q.front();
        q.pop();
        tem.push_back(node);
        for (int& p : ma[node]) {
            if (pa[p] >= 0) continue;
            pa[p] = node;
            q.push(p);
        }
    }
    vi maxx(n, -1), maxx2(n, -1);
    frep(i, n - 1, 0) {
        int tem2 = tem[i];
        maxx[tem2] = tem2;
        for (int& p : ma[tem2]) {
            if (pa[p] == tem2) {
                maxx[tem2] = max(maxx[tem2], maxx[p]);
                maxx2[tem2] = max(maxx2[tem2], maxx[p]);
            }
        }
    }
    vl dp(n), pre(n);
    rep(i, 0, n - 2) {
        ll tem2 = 0;
        if (maxx2[i] < i) {
            ll r = (i >= 1 ? pre[i - 1] : 0);
            ll l = (maxx2[i] >= 0 ? pre[maxx2[i]] : 0);
            tem2 = (r - l + MOD) % MOD;
        }
        if (i == idx) tem2 = (tem2 + 1) % MOD;
        dp[i] = tem2;
        if (i == 0)
            pre[i] = dp[i];
        else
            pre[i] = (pre[i - 1] + dp[i]) % MOD;
    }
    int pm1 = -1, pm2 = -1;
    for (int& p : ma[n - 1]) {
        if (pa[p] == n - 1) {
            if (maxx[p] > pm1) {
                pm2 = pm1;
                pm1 = maxx[p];
            } else if (maxx[p] > pm2) {
                pm2 = maxx[p];
            }
        }
    }
    ll tem2 = (pm2 >= 0 ? pre[pm2] : 0);
    cout << (pre[n - 2] - tem2 + MOD) % MOD << endl;
    return;
}

F

题目大意:给定长度为 $n$ 的数组 $a$ ,和长度为 $k$ 的数组 $b$ , $b$初始化为0。现在可以任意重排 $a$ ,然后按照顺序,每次将 $a_i$ 加入到 $b$ 中的最小值上,取 $f(a)$ 为最后 $b$ 的最大值,请求出所有 $f(a)$ 中的最大值。

数据范围: $1 \leq k \leq n \leq 18, 1 \leq a_i \leq 10^9$

思路:首先,这里的数据范围,看不出状压DP的建议回去补知识点了。

然后对于求出最大值,想到如果能满足大的,那么必然存在排列能满足小的,也就是存在单调性,那么很显然的可以使用二分答案。

但是这里需要发现一个性质,就是打表可以发现, $a$ 中的最大值(或者最大值之一),必然会在最后被加入 $b$ 。

然后,问题就转化为,用除了最大值的所有 $a_i$ ,是否存在一个顺序,使得它能按照题中所述放进 $b$ 中,使得每个桶都能达到 $mid$ 。

同时,还有一个观察,就是如果每个桶都能达到 $mid$ ,那么必然存在一个操作序列,使得能按照题中所述的操作方式得到结果。

因此考虑 $dp_i$ 为已经放了 $i$ 作为状压掩码时,已经达到至少 $mid$ 的桶数, $fdp_i$ 为已经放了 $i$ 作为状压掩码时,当前剩下的桶已经装进的元素之和。

从而有贪心的转移方程,也就是放入当前的数,如果能更新 $dp_{i \mid mask}$ ,那么就更新,如果是相等的,那就尽可能使当前剩下的比较多,也就是更新 $fdp_{i \mid mask}$ ,时间复杂度 $O(\log V n \cdot 2^n)$ 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void solve() {
    ll n, k;
    cin >> n >> k;
    vl a(n);
    rep(i, 0, n - 1) cin >> a[i];
    ranges::sort(a);
    ll tem = a[n - 1];
    ll tot = 0;
    rep(i, 0, n - 2) tot += a[i];
    n--;
    auto check = [&](ll mid) -> bool {
        int mask = (1 << n);
        vl dp(mask, -1);
        vl fdp(mask, -1);
        dp[0] = 0, fdp[0] = 0;
        rep(i, 0, mask - 1) {
            if (dp[i] == -1) continue;
            rep(j, 0, n - 1) {
                if ((i >> j) & 1) continue;
                ll tem = dp[i], tem2 = fdp[i];
                if (tem2 + a[j] >= mid) {
                    tem++, tem2 = 0;
                } else
                    tem2 += a[j];
                if (tem > dp[(i | (1 << j))]) {
                    dp[(i | (1 << j))] = tem;
                    fdp[(i | (1 << j))] = tem2;
                } else if (tem == dp[(i | (1 << j))] && tem2 > fdp[(i | (1 << j))]) {
                    fdp[(i | (1 << j))] = tem2;
                }
                if (dp[(i | (1 << j))] >= k) return true;
            }
        }
        return dp[mask - 1] >= k;
    };
    ll l = 0, r = tot / k, ans = 0, mid;
    while (l <= r) {
        mid = (l + r) / 2;
        if (check(mid)) {
            ans = mid;
            l = mid + 1;
        } else
            r = mid - 1;
    }
    cout << tem + ans << endl;
    return;
}