Atcoder-ABC156-DEF题解
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;
}