Codeforces Round #832 (Div. 2)

A. Two Groups (CF 1747 A)

题目大意

给定一个整数数组\(a\),要求将 \(a\)分成两部分\(s1,s2\),要求两部分的和的绝对值的差最大。

即最大化\(|\sum(s_1)| - |\sum(s_2)|\)

解题思路

数组\(a\)有正有负,要最大化差值的话,全部正数肯定全部放到 \(s1\),然后考虑负数放哪里。

负数放\(s1\)的话,差值肯定会减小,而放到\(s2\)的话差值也会减小。而如果把一些正数放到\(s2\),值也一样会减小。

因此答案就是 \(\sum(a)\)

当然因为是绝对值,如果负数和\(>\)正数和,那就把负数放到 \(s1\),正数放到 \(s2\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        LL sum = 0;
        for(int i = 0; i < n; ++ i){
            LL x;
            cin >> x;
            sum += x;
        }
        cout << abs(sum) << '\n';
    }
    return 0;
}


B. BAN BAN (CF 1747 B)

题目大意

定义\(s(n)\)为字符串 \(BAN\)重复 \(n\)次连接起来的字符串。

要求最小化对\(s(n)\)的操作,使得\(s(n)\)的任意子序列都不是 \(BAN\)

操作为,交换 \(s(n)\)的两个位置的字符。

解题思路

随便臆想一下,比如把第一个\(A\)和最后一个 \(N\)交换,第二个 \(A\)和倒数第二个 \(N\)交换,这样就OK了。

至于为什么是最小嘛(我也不知道,感觉每次操作都是拆散一个 \(BAN\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        vector<pair<int, int>> op;
        for(int i = 0, j = n - 1; i <= j; ++ i, -- j){
            int posA = i * 3 + 1;
            int posN = j * 3 + 2;
            op.push_back({posA + 1, posN + 1});
        }
        cout << op.size() << '\n';
        for(auto &i : op){
            cout << i.first << ' ' << i.second << '\n';
        }
    }

    return 0;
}


C. Swap Game (CF 1747 C)

题目大意

给定一个正整数数组\(a\)\(Alice\)\(Bob\)玩一个游戏, \(Alice\)先手,先后进行如此操作:

  • 如果 \(a[1]==0\),则输了
  • 选择 \(i > 1\) ,交换\(a[1]\)\(a[i]\),并将\(a[i]\)减一。

解题思路

关注数组\(a\)的最小值的位置。如果 \(a[1]\)是最小值,那么对于 \(Alice\)的操作,它都不能把最小值放在\(a[1]\)。 (\(Alice\)选择 \(i\),那么 \(Bob\)选择 \(i\), 那么最小值又回到 \(a[1]\)。此时\(Bob\)必胜。

而如果 \(a[1]\)不是最小值,那么\(Alice\)选择最小值那个下标,同样的道理 \(Bob\)也无法把最小值放在 \(a[1]\)。此时 \(Alice\)必胜。

因此如果\(a[1]\)是最小值,则 \(Bob\)必胜,否则是 \(Alice\)必胜。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n;
        cin >> n;
        vector<int> a(n);
        for(auto &i : a)
            cin >> i;
        if (a[0] <= *min_element(a.begin() + 1, a.end())){
            cout << "Bob" << '\n';
        }else{
            cout << "Alice" << '\n';
        }
    }

    return 0;
}


D. Yet Another Problem (CF 1747 D)

题目大意

给定一个自然数数组\(a\),回答 \(q\)组询问。

每组询问包含 \(L,R\),对 \(a[L..R]\)数组操作,每次操作 选择一个子区间,要求:

  • 该区间长度为奇数
  • 将该区间的每个数赋值为该区间的值的异或

要求最小化操作次数,使得\(a[L..R]\)变成 \(0\)

如果不能则输出 \(-1\)

解题思路

我们考虑一个区间的二进制下的某一位,如果其异或下值为\(1\),注意到区间长度是奇数,那么执行操作后,该区间的值还是不变。即操作不改变区间的异或值。

因此如果\(a[L..R]\)本身异或值不为 \(0\),则不可行。

如果为\(0\),且\(a[L..R]\)长度恰好为奇数,则直接选择该区间,操作一次即可。

如果为偶数,且恰好第一个数或最后一个数为 \(0\),则也可以选择长度为奇数的区间,操作一次即可。

否则,得操作两次,找到一个 \(a[L..M]\),使得其异或值为 \(0\), 此时\(a[M+1..R]\)异或值也为 \(0\) (因为\(a[L..R]\)异或值为 \(0\))。如果找不到则不可行。找到的话操作两次即可。

注意要保证区间长度为奇数。找的话可以下标按奇偶性分类后,二分符合条件的下标。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);
    int n, q;
    cin >> n >> q;
    vector<int> a(n + 1);
    vector<int> zero(n + 1);
    vector<int> xx(n + 1);
    unordered_map<int, vector<int>> pos[2];
    for(int i = 1; i <= n; ++ i){
        cin >> a[i];
        zero[i] = zero[i - 1] + (a[i] == 0);
        xx[i] = xx[i - 1] ^ a[i];
        pos[i & 1][xx[i]].push_back(i);
    }
    while(q--){
        int l, r;
        cin >> l >> r;
        if ((xx[r] ^ xx[l - 1]) != 0){
            cout << "-1" << '\n';
            continue;
        }
        if (zero[r] == zero[l - 1] + r - l + 1){
            cout << "0" << '\n';
            continue;
        }
        if ((r - l + 1) & 1){
            cout << "1" << '\n';
            continue;
        }
        if (a[l] == 0 || a[r] == 0){
            cout << "1" << '\n';
            continue;
        }
        int ans = 0;
        while(l <= r){
            int xsum = xx[l - 1];
            auto& target = pos[l & 1][xsum];
            auto smallR = upper_bound(target.begin(), target.end(), r);
            if (smallR == target.begin() || *(--smallR) < l){
                ans = -1;
                break;
            }
            int nxt = *smallR;
            debug(l, nxt, zero[l - 1], zero[nxt]);
            ans += (zero[nxt] != zero[l - 1] + nxt - l + 1);
            l = nxt + 1;
        }
        cout << ans << '\n';
    }
    return 0;
}



E. List Generation (CF 1747 E)

题目大意

给定\(n,m\),构造两个数组 \((a,b)\),满足:

  • \(len(a) = len(b) = k\)
  • \(a_0 = 0, b_0 = 0, a_k = n, b_k = m\)
  • \(a,b\)数组非递减

问能构造出来的 \((a,b)\)的所有 \(k\)的和。

解题思路

注意到\(a,b\)数组非递减,对其求差分数组。

枚举差分数组长度 \(k\), 则\(a,b\)数组长度是 \(k+1\)

然后问题就转换成:

  • \(n\)分配到 \(a\)的差分数组\(ca\)
  • \(m\)分配到 \(b\)的差分数组\(cb\)
  • 不存在下标\(i\),使得\(ca_i = cb_i = 0\)

求满足上述要求的方案数。

先把式子列出来。

枚举\(ca\)中非 \(0\)的位置,分配数使其和为 \(n\)。这个为经典的正整数方程解个数问题。

知道 \(ca\)\(0\)的位置,对于 \(cb\)来说,\(ca\)\(0\)的 位置的\(cb\)必须是正数,不为 \(0\)的则可以是自然数。其和为 \(m\)。这也是整数方程解个数问题。

因此式子为:

\[\sum_{k=1}^{\infty}(k+1) \sum_{l = 0}^{k}\tbinom{k}{l}\tbinom{n-1}{l-1}\tbinom{m+l-1}{k-1} \]

该表达式计算复杂度为\(O(n^2)\),得优化一下第二个求和。

第二个求和的三个组合数都跟\(l\)有关,我们的目标是尽量化成只有一个跟 \(l\)有关,但目测很难,尝试一下交换求和顺序。

\[\sum_{l=1}^{\infty}\tbinom{n-1}{l-1}\sum_{k = l}^{\infty}(k+1) \tbinom{k}{l}\tbinom{m+l-1}{k-1} \]

第二个求和还有两个组合数,观察其形式,可以用以下恒等式替换

\[\tbinom{n}{m}\tbinom{m}{k} = \tbinom{n}{k}\tbinom{n-k}{m-k} \]

\[\begin{align*} \sum_{l=1}^{\infty}\tbinom{n-1}{l-1}\sum_{k = l}^{\infty}(k+1) \tbinom{k}{l}\tbinom{m+l-1}{k-1} &= \sum_{l=1}^{\infty}\tbinom{n-1}{l-1}\sum_{k = l}^{\infty}\frac{(k+1)k}{l} \tbinom{k-1}{l-1}\tbinom{m+l-1}{k-1} \\ &= \sum_{l=1}^{\infty}\frac{\tbinom{n-1}{l-1}\tbinom{m+l-1}{l-1}}{l}\sum_{k = l}^{\infty}(k+1)k \tbinom{m}{k-l} \\ 仅仅为了不求那么多逆元 \to &= \sum_{l=1}^{\infty}\frac{\tbinom{n-1}{l-1}\tbinom{m+l-1}{l}}{m}\sum_{k = l}^{\infty}(k+1)k \tbinom{m}{k-l} \\ &= \sum_{l=1}^{n}\frac{\tbinom{n-1}{l-1}\tbinom{m+l-1}{l}}{m}\sum_{k = l}^{l + m}(k+1)k \tbinom{m}{k-l} \\ &= \sum_{l=1}^{n}\frac{\tbinom{n-1}{l-1}\tbinom{m+l-1}{l}}{m}\sum_{d = 0}^{m}(d + l + 1)(d + l) \tbinom{m}{d} \\ &= \sum_{l=1}^{n}\frac{\tbinom{n-1}{l-1}\tbinom{m+l-1}{l}}{m}\sum_{d = 0}^{m}(d^2 + (2l + 1)d + l(l + 1)) \tbinom{m}{d} \\ \end{align*} \]

由组合数恒等式可以得到

\[\begin{align*} \sum_{d = 0}^{m}d^2 \tbinom{m}{d} &= m(m+1)2^{m-2} \\ \sum_{d = 0}^{m}(2l + 1)d \tbinom{m}{d} &= (2l+1)m2^{m-1} \\ \sum_{d = 0}^{m}l(l + 1)\tbinom{m}{d} &= l(l+1)2^m \\ \end{align*} \]

因此

\[ans = \sum_{l=1}^{n}\tbinom{n-1}{l-1}\tbinom{m+l-1}{l}\frac{m(m+1)2^{m-2} + (2l+1)m2^{m-1} + l(l + 1) 2^m}{m} \]

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

const int N = 1e7 + 8;
const int mo = 1e9 + 7;

int bin(int x, int n, int MOD) {
    int ret = MOD != 1;
    for (x %= MOD; n; n >>= 1, x = 1ll * x * x % MOD)
        if (n & 1) ret = 1ll * ret * x % MOD;
    return ret;
}

inline int get_inv(LL x, LL p) { return bin(x, p - 2, p); }

vector<int> invf, fac;
void fac_inv_init(int n, int p) {
    FOR (i, 1, n)
        fac[i] = 1ll * i * fac[i - 1] % p;
    invf[n - 1] = bin(fac[n - 1], p - 2, p);
    FORD (i, n - 2, -1)
        invf[i] = 1ll * invf[i + 1] * (i + 1) % p;
}

int C(int n, int m){
    if (n < m)
        return 0;
    return 1ll * fac[n] * invf[m] % mo * invf[n - m] % mo;
}

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);

    fac.resize(N);
    fac[0] = 1;
    invf.resize(N);
    fac_inv_init(N, mo);
    int inv2 = get_inv(2, mo);
    int t;
    cin >> t;
    while(t--){
        int n, m;
        cin >> n >> m;
        int ans = 0;
        int tmp = 0;
        if (m == 1)
            tmp = inv2;
        else 
            tmp = bin(2, m - 2, mo);
        int zu1 = 1ll * tmp * 4 % mo;
        int zu2 = 1ll * m * tmp % mo * 2 % mo;
        int zu3 = 1ll * m * (m + 1) % mo * tmp % mo;
        for(int l = 1; l <= n; ++ l){
            int tmp1 = 1ll * C(n - 1, l - 1) * C(m + l - 1, l) % mo;
            int tmp2 = (0ll + zu3 + (2ll * l + 1) * zu2 % mo + 1ll * (l + 1) * l % mo * zu1 % mo) % mo;
            ans = (0ll + ans + 1ll * tmp1 * tmp2 % mo) % mo;
        }
        ans = 1ll * ans * get_inv(m, mo) % mo;
        cout << ans << '\n';
    }


    return 0;
}

还有另一种解法。注意到是非递减的,考虑在一个二维网格R,左下角\((0,0)\),右上角\((n,m)\),只能向右走和向上走,其路径上有一些关键点(对应的就是一个 \((a_i, b_i)\))。假设路径上有 \(k\)个关键点,有 \(cnt_k\)\(k\)个关键点的合法摆放(即有一条路径穿过这 \(k\)个关键点),答案就是 \(\sum_{k=0}^{\infty}k \times cnt_k\)

关键是如何求出\(cnt_k\)

观察一条路径,会发现有一些关键点是路径的拐点(向右后向上,或者向上后向右)。我们枚举其中一种拐点(比如向右后向上)数量\(i\),那再枚举 \(j\)个关键点在剩下的\(n+m-1-i\)点上 ,因此

\[\begin{align*} ans &= \sum_{i=0}^{\min(n, m)} \tbinom{n}{i} \tbinom{m}{i} \sum_{j=0}^{n + m - 1 - i}(i + j + 1)\tbinom{n+m-1-i}{j} \\ &= \sum_{i=0}^{\min(n, m)} \tbinom{n}{i} \tbinom{m}{i} ((i+1) 2^{n+m-1-i} + (n+m-1-i)2^{n+m-2-i}) \end{align*} \]

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)

const int N = 1e7 + 8;
const int mo = 1e9 + 7;

int bin(int x, int n, int MOD) {
    int ret = MOD != 1;
    for (x %= MOD; n; n >>= 1, x = 1ll * x * x % MOD)
        if (n & 1) ret = 1ll * ret * x % MOD;
    return ret;
}

inline int get_inv(LL x, LL p) { return bin(x, p - 2, p); }

vector<int> invf, fac;
void fac_inv_init(int n, int p) {
    FOR (i, 1, n)
        fac[i] = 1ll * i * fac[i - 1] % p;
    invf[n - 1] = bin(fac[n - 1], p - 2, p);
    FORD (i, n - 2, -1)
        invf[i] = 1ll * invf[i + 1] * (i + 1) % p;
}

int C(int n, int m){
    if (n < m)
        return 0;
    return 1ll * fac[n] * invf[m] % mo * invf[n - m] % mo;
}

int main(void) {
    ios::sync_with_stdio(false); 
    cin.tie(0); cout.tie(0);

    fac.resize(N);
    fac[0] = 1;
    invf.resize(N);
    fac_inv_init(N, mo);
    int inv2 = get_inv(2, mo);
    int t;
    cin >> t;
    while(t--){
        int n, m;
        cin >> n >> m;
        int ans = 0;
        if (n > m)
            swap(n, m);
        int tmp = bin(2, n + m - 1, mo);
        for(int l = 0; l <= n; ++ l){
            int t1 = C(n, l);
            int t2 = C(m, l);
            ans = (0ll + ans + 1ll * t1 * t2 % mo * ((l + 2ll) * (tmp * 2ll) % mo + (n + m - 1ll - l) * tmp % mo) % mo) % mo;
            tmp = 1ll * tmp * inv2 % mo;
        }
        ans = 1ll * ans * inv2 % mo;
        cout << ans << '\n';
    }


    return 0;
}

posted @ 2022-12-02 19:34  ~Lanly~  阅读(69)  评论(0编辑  收藏  举报