Atcoder-ABC156-DEF题解

Atcoder题解汇总

ABC 156

D. Bouquet (朴素求组合数,补集思想)

题意
\(n\) 种花里挑,每种花只有一个,最后花的数量不能是 \(a\) 或者 \(b\),求所有方案数 \(\mod 1e9+7\)

数据范围
\(2\leq n \leq 10^9\)
\(1\leq a < b \leq min(n, 2 * 10^5)\)

思路

  • 很容易想到用所有的方案数 - 选 \(a\) 个和 \(b\) 个的方案数
  • \(n\) 太大怎么办? 发现 \(a\)\(b\) 都在 \(2e5\) 范围内,由组合数公式 \(C_n^r = \frac{n!}{r!*(n-r)!}\), 可以在 \(O(a)\) 时间内求出
  • 预处理阶乘和逆元即可

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int mod = 1e9 + 7;

ll qmi(ll a, ll k, int mod) {
    ll res = 1;
    while (k) {
            if (k & 1)
                    res = res * a % mod;
            a = a * a % mod;
            k >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    ll n, a, b;
    cin >> n >> a >> b;
    ll c1 = 1, c2 = 1;
    for (int i = 1; i <= a; i ++) {
        c1 = c1 * (n - a + i) % mod * qmi(i, mod - 2, mod) % mod;
    }
    for (int i = 1; i <= b; i ++) {
        c2 = c2 * (n - b + i) % mod * qmi(i, mod - 2, mod) % mod;
    }

    cout << (qmi(2, n, mod) - c1 - c2 + 2 * mod - 1 ) % mod << endl;
    return 0;
}

E. Roaming (组合计数,隔板法)

题意

\(n\) 个房间,每个房间 \(1\) 个人,定义一个事件是某个人在 \(n\) 个房间中任意移动一次,但不能待在原地, 询问 \(k\) 次事件后,各房间有多少种情况,答案 \(\mod 1e9+ 7\)

数据范围
\(3\leq n \leq 2 * 10^5\)
\(2\leq k \leq 10^9\)

思路

  • 首先题目可以构造出所有房间仍然是 \(1\) 个人的情况,形成环即可。并且题目并不关心人的编号,只关心各房间情况
  • 先划分子集,按照最后情况中人数为 \(0\) 的房间的个数进行划分。
  • 假设有 \(m\) 个房间为空, 那么要在 \(n-m\) 个房间内塞 \(n\) 个人且不为空。
  • 问题等价于,对于 \(n\) 个小球中间的 \(n-1\) 个位置放置 \(n-m-1\) 个隔板,形成 \(n-m\) 个不为空的间隔的方案数。
  • 选择 \(m\) 个房间方案数为 \(C_n^m\) ,放置隔板方案数为 \(C_{n-1}^{n-m-1}\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/
const int N = 2e5 + 10, mod = 1e9 + 7;
ll fact[N], inv[N], n, k;

ll qmi(ll a, ll k, int mod) {
    ll res = 1;
    while (k) {
            if (k & 1)
                    res = res * a % mod;
            a = a * a % mod;
            k >>= 1;
    }
    return res;
}

ll C (int a, int b) {
    if (a < b) return 0;
    return fact[a] * inv[b] % mod * inv[a - b] % mod;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> k;
    ll ans = 0;
    fact[0] = 1;
    inv[0] = 1;
    for (int i = 1; i <= n; i++) {
        fact[i] = fact[i - 1] * i % mod;
        inv[i] = qmi(fact[i], mod - 2, mod);
    }
    for (int i = 0; i <= min(n - 1, k); i++) {
        ans = (ans + C(n, i) * C(n - 1, n - i - 1) % mod) % mod;
    }
    cout << ans << endl;
    return 0;
}

F. Modularness (补集,用商判断两数模意义下的大小)

题意

给了长度为 \(k\) 的数组 \(d\),下标从 0 开始。进行 \(q\) 次询问。
每次询问输入 \(n, x, m\), 有长度为 \(n\) 的数组 \(a\),下标从 0 开始。
其中:

\[ a_j = \begin{cases} x & \text{j = 0} \\ a_{j-1}+d_{{j-1}\mod m} & \text{0 < j < n} \end{cases} \]

对于每次询问,输出有多少 \(j\;(0<j<n-1)\) 满足 \((a_j\mod m) < (a_{j+1}\mod m)\)

数据范围
\(1\leq k,q \leq 5000\)
\(0\leq d_i \leq 10^9\)
\(2\leq n \leq 10^9\)
\(0\leq x \leq 10^9\)
\(2\leq m \leq 10^9\)

思路

  • 从题目可以看出每个 \(a_i\)\(d\) 的环形前缀和,并且对于每次询问将 \(d_i \% m\) 并不会影响答案。
  • 对于相邻的 \(a_i, a_{i+1}\) 其实只差一个 \(d_i\) ,如果对答案有贡献则表明 \((a_i + d_i) / m\) 没有增加。
  • 然而正面计算其实很困难,考虑求补集,即 \((a_j\mod m) >= (a_{j+1}\mod m)\)
    • 对于 \((a_j\mod m) = (a_{j+1}\mod m)\) 的情况, 只需要关心 \(d_i=0\) 的地方即可
    • 对于 \((a_j\mod m) > (a_{j+1}\mod m)\) 的情况,由于每个 \(d_i < m\) ,发生这种情况当且仅当 \(a_{j+1} / m = a_j / m + 1\)
      • 这个题的关键点就是这种局部的累积可以在末尾算出,即数量为 \(a_{n-1} / m - a_0/m = a_{n-1}m - x / m\)
      • \(a_{n-1}\) 是可以在 \(O(k)\) 时间内计算出来的,计算每个 \(d_i\) 的累加次数即可。注意计算 \(a_{n-1}\) 只需要进行 \(n-1\) 次加法,代码中的一个细节。
  • 时间复杂度 \(O(q*k)\)

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define arr(x) (x).begin(),(x).end()
#define x first
#define y second
#define pb push_back
#define endl "\n"
using namespace std;
/*----------------------------------------------------------------------------------------------------*/

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int k, q;
    cin >> k >> q;
    vector<ll> d(k, 0);
    for (int i = 0; i < k; i ++)
        cin >> d[i];
    while (q--) {
        int n, x, m;
        cin >> n >> x >> m;
        vector<ll> cd(k);
        int cnt = 0;
        for (int i = 0; i < k; i ++){
            cd[i] = d[i] % m;
        }
        ll a = x;
        n--;        // 实际进行 n - 1 次加法
        for (int i = 0; i < k; i++) {       // 计算补集
            a += (n / k) * cd[i];
            if (i < n % k)
                a += cd[i];
            if (!cd[i]) {   
                cnt += n / k;
                if (i < n % k)
                    cnt++;
            }
        }
        cnt += a / m - x / m;
        cout << n - cnt << endl;
    }
    return 0;
}
posted @ 2022-06-23 14:28  Roshin  阅读(86)  评论(0编辑  收藏  举报
-->