AtCoder 杂题集

ABC 453 E - Team Division(1513)

我们要把 \(N\) 个人分成 \(A\)\(B\) 两个具体的队伍(都不能为空),每个人对“自己所在队伍的总人数”有一个区间限制 \([L_i, R_i]\),求合法的分组方案数。

题解

枚举队伍 A 有 \(i\) 个人时,维护有多少人可以去队伍 A,多少人可以去队伍 B,多少人两者皆可以,多少人两者都不行,然后用组合数统计。维护这个信息需要用到两个差分数组,两个原数组的信息分别表示这个人在哪段区间可以去队伍A、在哪段区间可以两者都去。

view code
#include <bits/stdc++.h>
using namespace std;

using i64 = long long;

const int mod = 998244353;
int norm(int x) {
    if (x < 0) {
        x += mod;
    }
    if (x >= mod) {
        x -= mod;
    }
    return x;
}
template <class T>
T power(T a, i64 b) {
    T res = 1;
    for (; b; b /= 2, a *= a) {
        if (b % 2) {
            res *= a;
        }
    }
    return res;
}
struct Z {
    int x;
    Z(int _x = 0) : x(norm(_x)) {}
    Z(i64 _x) : x(norm(_x % mod)) {}
    int val() const { return x; }
    Z operator-() const { return Z(norm(mod - x)); }
    Z inv() const {
        assert(x != 0);
        return power(*this, mod - 2);
    }
    Z& operator*=(const Z& rhs) {
        x = i64(x) * rhs.x % mod;
        return *this;
    }
    Z& operator+=(const Z& rhs) {
        x = norm(x + rhs.x);
        return *this;
    }
    Z& operator-=(const Z& rhs) {
        x = norm(x - rhs.x);
        return *this;
    }
    Z& operator/=(const Z& rhs) { return *this *= rhs.inv(); }
    friend Z operator*(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res *= rhs;
        return res;
    }
    friend Z operator+(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res += rhs;
        return res;
    }
    friend Z operator-(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res -= rhs;
        return res;
    }
    friend Z operator/(const Z& lhs, const Z& rhs) {
        Z res = lhs;
        res /= rhs;
        return res;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<int> l(n), r(n);
    vector<int> diff(n + 1), diff2(n + 1);
    for (int i = 0; i < n; i++) {
        cin >> l[i] >> r[i];
        diff[l[i]]++;
        diff[r[i] + 1]--;
        int lo = max(l[i], n - r[i]);
        int hi = min(r[i], n - l[i]);
        if (lo <= hi) {
            diff2[lo] += 1;
            diff2[hi + 1] -= 1;
        }
    }
    vector<int> cnt(n + 1), both(n + 1);
    for (int i = 1; i <= n; i++) {
        cnt[i] = cnt[i - 1] + diff[i];
        both[i] = both[i - 1] + diff2[i];
    }

    vector<Z> fact(n + 1), inv_fact(n + 1);
    fact[0] = inv_fact[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i;
        inv_fact[i] = 1 / fact[i];
    }

    auto C = [&](int N, int M) {
        return fact[N] * inv_fact[M] * inv_fact[N - M];
    };

    Z ans = 0;
    for (int i = 1; i < n; i++) {
        int sa = cnt[i];
        int sb = cnt[n - i];
        int x = both[i];
        int d = n - sa - sb + x;
        if (d > 0) continue;
        int ca = sa - x;
        int need = i - ca;
        if (need < 0 || need > x) continue;
        ans += C(x, need);
    }

    cout << ans.val() << '\n';
}

 

AGC 044 A - Pay to Win(1828)

要把起始为 0 的数变成 N, 可以乘 2, 3, 5 或加减 1, 它们分别需要代价 \(A,B,C,D\). 问最小代价. \(N ≤ 1e18, A,B,C,D ≤ 1e9\).

题解

把整个过程反过来考虑. 注意到在除 \(k\) 前进行 \(+1/-1\) 是不会超过 \(k\) 的整数倍的. 例如, 当前为 31, 那么一定不会加减超过 30 或 32 再除以 2. 这意味着当前为 N 时, 以每次进行除法为划分, 下一次只会转移到 \(\lfloor \frac{N}{k} \rfloor\)\(\lceil \frac{N}{k} \rceil\), \(k = 2, 3, 5\). 进行记忆化搜索就可以解决问题.

我们还要知道会到达多少种状态. 考虑每次除以 2, 3, 5次数一样的情况, 无论向上还是向下取整, 它们的取值至多差 1. 再计算一下\(log_{2}{1e18}\approx 60, log_{3}{1e18}\approx 40 , log_{5}{1e18}\approx 30\). 所以可能到达的状态至多有 \(2*60*40*30\approx 1.5e5\) 种. 这还只是一个粗略的上限, 因为这包含了部分乘积大于 1e18 的情况.

我的代码写得很搓, 这里推荐一波杜老师的代码.

 

AGC 043 B - 123 Triangle(1945)

题意: \(N × N\) 的上三角型矩阵, 给出第一行的元素, 并且取值都在\([1, 3]\). 其他元素满足\(a_{i,j}=|a_{i-1,j}-a_{i-1,j+1}|\). 问\(a_{N,1}\)的值. \(N ≤ 1e6\).

题解

显然第一行的数都 -1 后不影响答案. 此时所有元素的取值范围都是\([0,2]\). 如果推导式中没有取绝对值的话, 可以直接套二项式系数.

在取绝对值意义下, 奇偶性是不会改变的, 所以我们可以套二项式系数算出\(a_{N,1}\)的奇偶. 如果是奇数, 结果就是 1. 如果是偶数, 需要考虑结果是 0 还是 2. 如果第一行的元素中有 1, 那么结果一定不会是 2. 如果没有 1, 那么对整个矩阵除以 2, 对新矩阵再套一次二项式系数, 最后结果乘以 2 即可.

posted @ 2020-05-24 12:06  Linqi05  阅读(25)  评论(0)    收藏  举报