Codeforces Round 1108 (Div. 2)

在复健补题,由于多年退化水平比较低下,所以没有 E 和 F。

A. farmpiggie and Subset Sum

题意

考虑一个长度为偶数 \(n\) 的排列 \(p\),这个排列会与它的下标相乘,得到一组数:

\[1 \cdot p_1, \; 2 \cdot p_2, \; 3 \cdot p_3, \; \dots, \; n \cdot p_n \]

对于这 \(n\) 个数,每一个数你都可以选择赋予它正号(\(+\))、负号(\(-\))或者不选(即乘以 \(0\)),然后对它们求和。

构造任意一个排列 \(p\) 并输出,使得不管怎么拼凑这几个数的符号,都无法让求和结果等于 1。

思路

考虑奇偶性,最容易想到的就是令每个 \(|i \cdot p_i|\) 都是偶数,即要么 \(i\) 为偶数,要么 \(p_i\) 为偶数,此时求和结果一定为偶数。

由这个思路,可以立即想到,从 \(\{1, 2, 3, 4, \dots, 2k-1, 2k\}\) 这样的标准排列开始构造,可以考虑:

  • 偶数位置上的数和奇数位置上的数进行两两交换。特别地,为了写代码方便,可以让相邻数之间交换:\(\{2, 1, 4, 3, \dots, 2k, 2k-1\}\)
  • 直接倒序输出:\(\{2k, 2k-1, \dots, 2, 1\}\)

即可通过本题。

B. ezraft and Array

题意

给定一个正整数 \(n\),构造一个含 \(n\) 个不相同正整数的数组,使得数组之和能被所有数组元素整除。

思路

考虑迭代构造。\(n = 1\) 略,\(n = 2\) 显然无解。

\(n = 3\) 开始考虑,显然有 \([1, 2, 3]\) 满足题意。

现在考虑从 \(n\) 递推到 \(n+1\):对于满足题意的数组 \(a_n\),一个很自然的想法是把 \(\sum(a_n)\) 塞入到原来的 \(a_n\) 数组里来构造新的数组 \(a_{n+1}\),此时新数组的和为 \(2 \sum{a_n}\),而:

  • \(\sum{a_n}\) 能够被原数组里所有的正整数 \(a_1, \dots, a_n\) 整除,这是原数组 \(a_n\) 满足的条件
  • \(2 \sum{a_n}\) 又显然能够被 \(a_{n + 1} = \sum{a_n}\) 整除

于是直接从 \(\{1, 2, 3\}\) 开始递推求解数组就可以了。

C. 0mar and Alternating Sums

题意

定义一个数组 \(b\)交错和(alternating sum) 为:\(\sum_{i = 1}^k (-1)^{i+1} b_i\)

给定一个长度为 \(n\) 的不下降数组 \(a\),元素 \(a_i\) 满足:要么是 \(-1\),要么是正整数。现在找出数组 \(a\) 中所有满足交错和为 \(0\) 的子序列,对结果取模 \(10^9 + 7\)

思路

先考虑第一种情况:数组中没有 \(-1\)

首先,显然子序列的长度必须为偶数,设其长度为 \(2k\)。任取子序列中的两项 \(c_{2m-1}, c_{2m}\),对交错和的贡献为 \(c_{2m - 1} - c_{2m}\);又因为原数组不下降且都为正,则 \(c_{2m - 1} - c_{2m} \le 0\)。要想子序列的交错和为 0,则进一步必须满足 \(\forall m, c_{2m-1} = c_{2m}\)

有了这个结论之后,回到原数组筛选子序列。考虑 \(a\) 中所有元素相同的一个极大子数组 \(sa\),假设其长度为 \(k\),必须在其中选择偶数个值才能保证交错和为 0,否则 \(c_{2m - 1} \ne c_{2m}\)。方案数量为:

\[C_{k}^0 + C_{k}^2 + C_{k}^4 + \dots = 2^{k-1} \]

考虑乘法原理,统计所有元素相同的极大子数组的大小 \(k_1, k_2, k_3, \dots\),此时 \(\prod_i 2^{k_i-1}\) 即为答案。

现在考虑第二种情况:数组中有 \(-1\)

如果在子序列中引入偶数个 \(-1\),则这部分对交错和的贡献会相互抵消成 \(0\),因此子序列中 \(-1\) 之后的部分对交错和的贡献仍然需要为 \(0\),这退化到了第一种情况;否则,如果在子序列中引入奇数个 \(-1\),此时需要后半部分的交错和贡献需要为 \(+1\)

很容易想到这怎么实现:对于数组内元素相同的两个极大子数组 \(sa_1, sa_2\),如果它们的元素值分别为 \(a_{sa_1}, a_{sa_2}\)\(a_{sa_2} - a_{sa_1} = 1\),即整数值相邻,那么可以从这两个子数组里各取奇数个值,其他子数组里取偶数个值,方案数仍然为 \(\prod_i 2^{k_i-1}\)

综合这两种情况,答案计算如下:

  • 当数组中不存在 \(-1\) 时,方案数为 \(\prod_i 2^{k_i-1}\)
  • 当数组中存在 \(-1\) 时,方案数为 \((d + 1) \cdot \prod_i 2^{k_i-1}\)\(d\) 为原数组元素去重之后,满足整数值相邻的对数

代码

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
typedef long double ld;
typedef std::vector<int> vi;
 
#define forn(i, a, b) for(int i = a; i < b; i++)
#define readi(x) scanf("%d", &x)
#define readv(x, n) forn(i,0,n) scanf("%d", &x[i])
 
#ifdef DEBUG
#define printi(x) printf("DEBUG: %d\n", x)
#define prints(x) printf("%s", x)
#define printv(x, n) printf("DEBUG: "); forn(i,0,n) printf("%d ", x[i])
#else
#define printi(x) printf("%d\n", x)
#define prints(x) cout << x << endl
#define printv(x, n) forn(i,0,n) printf("%d ", x[i])
#endif

const ll MOD = 1e9 + 7;
const int N  = 2e5 + 5;

ll _pow[N];

void pre(void) {
    _pow[0] = 1;
    forn (i, 1, N)
        _pow[i] = (_pow[i - 1] << 1) % MOD;
}
 
int main(void){
    pre();

    int t;
    readi(t);
    while (t--) {
        int n;
        readi(n);
        
        ll ans = 0, _exp = 1;
        bool is_minus_one_exist = false;
        int last_num = -100, cnt = 0, adj_num = 0;
        while (n--){
            int tmp;
            readi(tmp);

            if (tmp == -1)
                is_minus_one_exist = true;

            if (tmp == last_num)
                cnt++;

            else {
                if (tmp - last_num == 1)
                    adj_num++;
                _exp += cnt - 1;

                last_num = tmp;
                cnt = 1;
            }
        }
        _exp += cnt - 1;

        if (is_minus_one_exist)
            ans = (adj_num + 1) * _pow[_exp];
        else
            ans = _pow[_exp];

        printf("%lld\n", ans % MOD);
    }
}

D. diss_quack and Array Game

题意

给定一个含有 \(n\) 个整数的数组 \(a\),Alice 和 Bob 将在这个数组上进行游戏。

在与 Bob 的游戏正式开始之前,Alice 可以任意多次选择数组中的一个元素,将其加 \(1\)。每次对一个元素加 \(1\) 都算作 Alice 的一步操作。

随后游戏开始,Bob 和 Alice 轮流行动,Bob 先手。

在 Bob 的回合中,他可以任意选择两个下标 \(i\)\(j\),交换 \(a_i\)\(a_j\)。特别地,Bob 可以选择 \(i=j\),此时数组不会发生变化。

在 Alice 的回合中:

  • 如果 \(a_1\) 是偶数,她会找到最大的 \(j\le |a|\),使得对于所有 \(i\le j\)\(a_i\) 都是偶数;随后对所有 \(i\le j\),令 \(a_i:=a_i/2\)
  • 否则,令 \(a_1:=a_1-1\)

如果某个元素在操作后变成了 \(0\),它会立刻从数组中被删除,其余元素的下标也会相应地重新排列。

当数组变为空数组时,游戏结束。

Alice 希望最小化自己执行的操作次数,而 Bob 希望最大化 Alice 的操作次数。这里 Alice 的总操作次数等于:

  • 游戏开始前,Alice 对数组元素执行加 \(1\) 的次数;
  • 游戏开始后,Alice 行动的回合数。

求双方都采取最优策略时,Alice 最终需要执行多少次操作。

思路

考虑游戏正式开始之后(即 \(a\) 数组固定),Bob 会如何决策,使得 Alice 的操作次数最大化。

不难想到,如果数组 \(a\) 中存在奇数,Bob 可以在整个游戏过程中一直把这个奇数放在第二个位置,这样不管 Alice 怎么操作,都只会影响第一个数;当其他数都被消耗完之后,再把这个奇数消耗掉即可。记 \(a_i \in a\) 的二进制长度为 \(l_i\),二进制形式中 \(1\) 的个数为 \(c_i\),容易知道此时 Alice 的总步数为 \(\sum_i f(a_i) = \sum_i (l_i + c_i - 1)\)

而如果数组 \(a\) 中全为偶数,则不管 Bob 怎么操作,Alice 都会先把整个数组除以 \(2\),直到数组 \(a\) 中出现奇数为止,此时又回到了第一种情况。

既然 Alice 可以在游戏开始之前,对数组 \(a\) 中的元素执行加 \(1\) 操作,那么该怎么样才能让操作次数减小呢?

第一次错误

我最开始考虑,如果能在较少的加 \(1\) 操作下,让数组的所有元素都能被 \(2^k\) 次方整除,那么应该就会省下一些操作次数,而这个 \(k\) 是可以穷举的。比如考虑以下样例:

\[1111100, 1100111, 1001100, 1000100, \dots(99996 个元素) \]

很显然,对第二个数加 \(1\) 之后,因为整个数组的元素都被 \(4\) 整除,可以很大程度上减少操作次数,而且在这种极端数据下,省下的操作次数是十万量级的。

然而,这个贪心是不充分的,考虑以下反例:

\[11111, 1 \]

\(k = 0\) 时,不改变数组,此时操作次数为 \(9 + 1 = 10\);当 \(k = 1\) 时,第一个数和第二个数都 \(+1\),此时操作次数为 \(2 + 1 + 5 + 1 = 9\)。然而,更优的做法是只给第一个数 \(+1\),此时操作次数为 \(1 + 6 + 1 = 8\)

这个反例暴露的问题是,除了要考虑整除对答案的全局贡献(对 \(l_i\) 生效)之外,还需要考虑局部贡献,即减少每个元素的二进制形式中 \(1\) 的个数对答案的贡献(对 \(c_i\) 生效),而局部贡献是各元素独立的,也就是可以分别枚举。

在同时考虑这两个变量的情况下,应该如何贪心?

第二次错误

这里我又想错了。首先每个数能省下的步数不多,具体来说,最多也就是 \(17 + 17 - 1 = 33\) 步。那我想先单独处理局部贪心,也就是先找到 \([a_i, a_i + 33]\)\(l_i + c_i - 1\) 最小的数,记为 \(a_{i_2}\),再考虑全局贪心,把每个 \(a_{i_2}\) 都拉到比它大的第一个能被 \(2^k\) 整除的数 \(a_{i_3}\)

但是这个局部优化没有考虑到全局影响,事实上也确实能构造反例:

\[1972(0111~1011~0100), 1017(0011~1111~1001) \]

在局部优化阶段,\(f(1017) = 17, f(1024) = 11\),而 \(11 + 7 = 18 > 17\),会多出一个操作次数,所以没有数会被改变;而在全局优化阶段,当 \(k=2\) 时,如果将 \(1017\) 变成 \(1020\),则操作次数会变多 1(\(3 + 2 - 2 \times 2 = 1\)),所以也不会触发优化。

但事实上,如果只把 \(1017\) 拉到 \(1024\),虽然会多出 \(7\) 步操作,但是局部优化部分(\(c_i\))减少了 \(6\) 步操作,全局优化部分(\(l_i\))减少了 \(2\) 步操作,两个优化加起来是更优的。也就是说这里的全局优化出现了短视的问题,没有考虑局部优化。

正解

那当然是在全局优化里考虑局部优化。具体地:

  • 全局优化(外层循环):枚举 \(k\),将所有的数都对齐到能被 \(2^k\) 整除,这个被对齐的数记为 \(b_i\)
  • 局部优化(内层循环):在 \(b_i\) 的基础上,枚举 \([b_i. b_i + 33]\) 中所有能被 \(2^k\) 整除的数,然后找到其中操作次数最少的数

代码

#include <bits/stdc++.h>
#include <bit>
using namespace std;
 
typedef long long ll;
typedef long double ld;
typedef std::vector<int> vi;

#define INT_INF 2147483647
 
#define forn(i, a, b) for(int i = a; i < b; i++)
#define readi(x) scanf("%d", &x)
#define readv(x, n) forn(i,0,n) scanf("%d", &x[i])
 
#ifdef DEBUG
#define printi(x) printf("DEBUG: %d\n", x)
#define prints(x) printf("%s", x)
#define printv(x, n) vi x(n); printf("DEBUG: "); forn(i,0,n) printf("%d ", x[i])
#else
#define printi(x) printf("%d\n", x)
#define prints(x) cout << x << endl
#define printv(x, n) forn(i,0,n) printf("%d ", x[i]); printf("\n")
#endif
 
int main(void){
    int t;
    readi(t);
    while (t--) {
        int n;
        readi(n);

        vi x(n);
        readv(x, n);

        ll ans = INT_INF;
        for (int v = 1, w = 0; v <= 200000; v <<= 1, w += 1) {
            ll _ans = 0;
            forn (i, 0, n) {
                int gap = (v - x[i] % v) % v;
                _ans += gap;

                int _min_step = INT_INF;
                for (int k = 0; k <= 33; k++) {
                    unsigned t = x[i] + gap + k;
                    if (t % v == 0) {
                        _min_step = min(_min_step, k + std::popcount(t) + std::bit_width(t) - 1);
                    }
                }
                _ans += _min_step;
            }

            _ans -= (n - 1) * w;
            ans = min(ans, _ans);
        }

        printf("%lld\n", ans);
    }
}
posted @ 2026-07-17 00:48  Pride205  阅读(48)  评论(0)    收藏  举报