题目链接
不难发现,当人数是奇数和偶数时,情况稍有不同。先看简单的偶数情况:
以 n = 8 n=8 n=8为例。对于1 2 3 4 5 6 7 8,如果我们当前要走的步数是 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4,那么所求的答案就非常简单,求第 i i i个被删掉的数,就是 2 i 2i 2i。但如果求第 i ( 2 i > n ) i(2i>n) i(2i>n)时,由于整个数列要围城一个环,所以我们又会折回到最左侧继续寻找,而这个时候答案就不是简单的 2 i 2i 2i了,因为有些数已经被删掉了,数 2 2 2次删一个数的过程中,遇到这些被删除的数是不会让计数器 + 1 +1 +1的。所以暴力遍历寻找是会超时的。
于是可以寻找子问题。原问题是1 2 3 4 5 6 7 8,有8个数,要走 i ( i > n ) i(i>n) i(i>n)步,我们可以先走 4 4 4步,于是数列变成了1 3 5 7,还要走 i − 4 ( i > n ) i-4(i>n) i−4(i>n)步。假设现在有一个子问题,问有1 2 3 4,要走 i − 4 i-4 i−4步,删掉的数是哪个,假设这个问题的答案是 a n s ans ans,我们可以通过 a n s ans ans求出原问题的答案是 2 a n s − 1 2ans - 1 2ans−1。这样不断递归寻找子问题的答案再原路返回,就可以求出这个原问题的答案了。我们发现,每次递归,数列中的数都会除以 2 2 2,所以递归次数不会超过 log n \log n logn次。
递归的临界态,是当原数列只有 1 1 1个数时,那么这个子问题答案必然是 1 1 1,不需要再往下递归了。
用同样的方法看奇数情况。
以 n = 7 n=7 n=7为例,对于1 2 3 4 5 6 7。这里和偶数不同的是,由于整个环的个数是奇数,所以第二轮从左到右遍历时的起点和第一轮不同,所以我们要对这一点进行一个适当的处理。
假设求走 i ( 2 i < n ) i(2i
如果当前的问题可以直接一轮求得结果,就不需要再向下递归求解子问题了,可以直接返回结果。
#include
using namespace std;
typedef long long LL;
int dfs(int n, int m) {
// cout << "n = " << n << " m = " << m << '\n';
if (n == 1) return 1;
if (n % 2 == 0) {
if (2 * m <= n) return 2 * m;
else {
int tmp = dfs(n / 2, m - n / 2);
return (tmp * 2 - 1);
}
}
if (n % 2 == 1) {
if (2 * m <= n) return 2 * m;
else if (2 * m == n + 1) return 1;
else {
int tmp = dfs(n / 2, m - (n + 1) / 2);
return (tmp * 2 + 1);
}
}
}
int n, m;
void main2() {
cin >> n >> m;
cout << dfs(n, m) << '\n';
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
while (_--) main2();
return 0;
}
题目链接
快速幂求解即可。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
template<class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
LL a, b;
void main2() {
cin >> a >> b;
cout << power(a, b) << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
while (_--) main2();
return 0;
}
题目链接
由于我们的模数是一个质数,设模数为 m = 1 0 9 + 7 m=10^9+7 m=109+7,那么 φ ( m ) = m − 1 \varphi (m)=m-1 φ(m)=m−1。
根据扩展欧拉定理, a b ≡ a b m o d φ ( m ) a^b \equiv a^{b\mod \varphi(m)} ab≡abmodφ(m)。所以套两层快速幂即可。
#include
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
template<class T> T power(T a, LL b, LL mod) {
if (a == 0 and b == 0) return 1;
T res = 1ll;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
LL a, b, c;
void main2() {
cin >> a >> b >> c;
cout << power(a, power(b, c, MOD - 1), MOD) << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
while (_--) main2();
return 0;
}
题目链接
有一种在调和级数时间复杂度内找到 n n n以内所有因子的方法,第一层循环 i i i从 1 1 1到 n n n枚举所有的因子,第二层循环 j j j从 i + i i+i i+i到 n n n,枚举所有 i i i的倍数,这样 i i i就作为 j j j的其中一个因子,加入到对 j j j的贡献中。这道题中的贡献为 1 1 1。
#include
using namespace std;
typedef long long LL;
int fac[1000005];
void init() {
for (int i = 2; i <= 1000000; ++i) {
for (int j = i; j <= 1000000; j += i) {
++fac[j];
}
}
}
int x;
void main2() {
cin >> x;
cout << fac[x] + 1 << '\n';
}
int main() {
// freopen("Fin.in", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
cin >> _;
init();
while (_--) main2();
return 0;
}
题目链接
有一种最后一个点会T的方法:对于所有的数,我们 O ( a i ) O(\sqrt{a_i}) O(ai)统计其所有因子,每有一个因子,将那个因子计数 + 1 +1 +1。最后从大到小看每一个因子, O ( max a i ) O(\max{a_i}) O(maxai)找到最大的因子数量大于 1 1 1的数。这个时间复杂度是 O ( n max a i + n ) O(n\sqrt{\max{a_i}}+n) O(nmaxai+n)的,不太可以接受。
我们可以换一个角度考虑,可以从调和级数求因子入手,我们先读入所有 a i a_i ai,统计序列中每一个 a i a_i ai出现的次数。然后我们从大到小枚举所有可能的因子,看看他们的倍数出现过几次。如果出现过超过 1 1 1次,那么可以认为这个就是所求答案。
#include
using namespace std;
typedef long long LL;
int n;
int a[1000005];
void main2() {
cin >> n;
for (int i = 1; i <= n; ++i) {
int x; cin >> x;
++a[x];
}
for (int i = 1000000; i >= 1; --i) {
int tmp = 0;
for (int j = i; j <= 1000000; j += i) {
tmp += a[j];
}
if (tmp > 1) {
cout << i;
return;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
数据范围达到 1 0 12 10^{12} 1012,所以 O ( n ) O(n) O(n)是不可行的。
考虑 O ( n ) O(\sqrt{n}) O(n)枚举因子,计算因子贡献。我们既要考虑 n \sqrt{n} n以内的因子,也要考虑与这些因子成对的因子。我们用 i i i从 1 1 1到 n \sqrt{n} n枚举,对于每一个 i i i,考虑对答案的贡献:
i i i可以作为所有 i i i的倍数的因子,考虑到枚举前面因子的时候会有一些 i i i会作为与那些因子成对的因子被计算,可以发现,小于 i 2 i^2 i2的数的因子 i i i会在之前就被计算过(可以手算一下,试着模拟一下过程就会发现这一点),所以我们在讨论 i i i时,就只讨论大于等于 i 2 i^2 i2的因子。那么作为小因子, i 2 i^2 i2会出现 n i − i + 1 \frac{n}{i}-i+1 in−i+1次。然后计算由这个 i i i产生的大因子的贡献,会发现可以用大于等于 i 2 i^2 i2的所有 i i i的倍数的和除以 i i i来表示,而这个和就是 i + 1 i+1 i+1到 n i \frac{n}{i} in的等差数列的和。将这两个贡献加到一起,就是 i i i产生的贡献。
最后求和就是答案。时间复杂度 O ( n ) O(\sqrt{n}) O(n)。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
const LL mod2 = 500000004;
LL n, ans;
LL calc(LL l, LL r) {
if (l > r) return 0;
return (l + r) % mod * ((r - l + 1) % mod) % mod * mod2 % mod;
}
void main2() {
cin >> n;
ans = 0;
for (LL i = 1; i * i <= n; ++i) {
ans = (ans + i * (n / i - i + 1) + calc(i + 1, n / i)) % mod;
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
这道题有三个小问,我们一一来看。
第一个是要求因子的数量。我们得到的数是质因数分解的形式,所以我们可以考虑把每一个质因数当作一个物品来看待,我们对于每一个质因数都有 b i b_i bi种选法,将这 n n n个质因数的每一个的取法种数乘到一起,就是可以取的因子的种数。
第二个是求因子的和。我们可以从前往后逐个考虑质因子。在考虑第 i i i个质因子的时候,我们考虑如何从前 i − 1 i-1 i−1个质因子的结果中转移过来。我们设由前 i i i个质因子构成的所有因子和答案是 S i S_i Si,且令 S 0 = 1 S_0=1 S0=1。考虑前 i − 1 i-1 i−1个质因子所构成的所有因子的和 S i − 1 S_{i-1} Si−1,当前第 i i i个质因子可以为前面的所有形成的因子乘上 1 , a i , a i 2 , a i 3 , ⋯ , a i b i 1,a_i,a_i^2,a_i^3,\cdots, a_i^{b_i} 1,ai,ai2,ai3,⋯,aibi。所以可得: S i = S i − 1 × ( 1 + a i + a i 2 + a i 3 + ⋯ + a i b i ) S_i=S_{i-1}\times (1+a_i+a_i^2+a_i^3+\cdots+a_i^{b_i}) Si=Si−1×(1+ai+ai2+ai3+⋯+aibi),后面的可以用等比数列前 n n n项和的公式化简,得到 S i = S i − 1 × 1 − a i b i 1 − a i S_i=S_{i-1}\times \frac{1-a_i^{b_i}}{1-a_i} Si=Si−1×1−ai1−aibi。
第三个是求因子的积。我们同样从前往后逐个考虑质因子。在考虑第 i i i个质因子的时候,我们考虑如何从前 i − 1 i-1 i−1个质因子的结果中转移过来。我们设由前 i i i个质因子构成的所有因子积的答案是 P i P_i Pi,且令 P 0 = 1 P_0=1 P0=1。考虑前 i − 1 i-1 i−1个质因子所构成的所有因子的积是 P i − 1 P_{i-1} Pi−1,当前第 i i i个因子可以为前面所有形成的因子乘上 1 , a i , a i 2 , a i 3 , ⋯ , a i b i 1,a_i,a_i^2,a_i^3,\cdots, a_i^{b_i} 1,ai,ai2,ai3,⋯,aibi。前面的所有因子都可以选择乘上这 b i + 1 b_i+1 bi+1个数中的任意一种,所以会凭空从原来的 x x x个因子变成 ( b i + 1 ) x (b_i+1)x (bi+1)x个因子,不难想到,光是原来的因子现在乘起来就变成了 P i − 1 1 + b i P_{i-1}^{1+b_i} Pi−11+bi。然后,对于 1 , a i , a i 2 , a i 3 , ⋯ , a i b i 1,a_i,a_i^2,a_i^3,\cdots, a_i^{b_i} 1,ai,ai2,ai3,⋯,aibi中的每一个数,都乘了由前 i − 1 i-1 i−1个质因子所组成的所有因子的个数次。而这个因子的个数,是 ( b 0 + 1 ) ( b 1 + 1 ) ( b 2 + 1 ) ⋯ ( b i − 1 + 1 ) (b_0+1)(b_1+1)(b_2+1)\cdots (b_{i-1}+1) (b0+1)(b1+1)(b2+1)⋯(bi−1+1)。每次求完 P i P_i Pi后,更新一下这个因子的个数即可。设前 i − 1 i-1 i−1个质因子所组成的因子个数是 C i − 1 C_{i-1} Ci−1,那么 P i = P i − 1 1 + b i × ( 1 × a i × a i 2 ⋯ a i b i ) C i − 1 P_i=P_{i-1}^{1+b_i}\times (1\times a_i \times a_i^2 \cdots a_i^{b_i})^{C_{i-1}} Pi=Pi−11+bi×(1×ai×ai2⋯aibi)Ci−1,而整个括号里面的底数相同,幂数可以用等差数列求个和,于是式子变成 P i = P i − 1 1 + b i × ( a i ( 1 + b i ) b i / 2 ) C i − 1 P_i=P_{i-1}^{1+b_i}\times (a_i^{(1+b_i)b_i/2})^{C_{i-1}} Pi=Pi−11+bi×(ai(1+bi)bi/2)Ci−1。
在用快速幂的时候要注意,根据费马小定理,幂数取模时要对 m o d − 1 mod-1 mod−1取模(因为满足 m o d mod mod是个质数)。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
template <class T> void exgcd(T a, T b, T &x, T &y) {
if (!b) x = 1, y = 0;
else exgcd(b, a % b, y, x), y -= a / b * x;
}
template <class T> T inverse(T a, T p) {
LL x, y; exgcd(a, p, x, y);
return (x % p + p) % p;
}
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
LL calc_mul(LL a, LL b) {
return (power(a, b + 1) + mod - 1) % mod * inverse((a + mod - 1) % mod, mod) % mod;
}
LL calc_sum(LL a, LL b, LL MOD) {
return ((a + b) * (b - a + 1) / 2) % MOD;
}
LL n;
LL a[100005], b[100005];
LL ans[3];
LL sum = 0;
void main2() {
cin >> n;
for (int i = 0; i < 3; ++i) {
ans[i] = 1ll;
}
sum = 0;
for (int i = 1; i <= n; ++i) {
cin >> a[i] >> b[i];
ans[0] = (ans[0] * (b[i] + 1)) % mod;
sum += (b[i] + 1);
ans[1] = (ans[1] * calc_mul(a[i], b[i])) % mod;
}
LL cnt = 1;
for (int i = 1; i <= n; ++i) {
LL tmp = calc_sum(1ll, b[i], mod - 1);
ans[2] = (power(ans[2], (b[i] + 1) % (mod - 1)) * power(power(a[i], tmp), cnt)) % mod;
cnt = (cnt * (b[i] + 1)) % (mod - 1);
}
for (int i = 0; i < 3; ++i) {
cout << ans[i] << ' ';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
while (_--) main2();
return 0;
}
题目链接
n n n的范围很大,对于每一个质数 p p p,符合要求的 p p p的倍数的个数是 ⌊ n p ⌋ \lfloor \frac{n}{p} \rfloor ⌊pn⌋个。但是这样的计算方法存在重复计数,所以要利用容斥原理,将一些重复的计数去掉。
由于这道题目的质数的数量只有 20 20 20,枚举其所有可能的质数相乘是只有 2 20 2^{20} 220种可能,这种枚举是不会超时的
#include
using namespace std;
typedef long long LL;
LL n, k;
LL a[25];
void main2() {
cin >> n >> k;
for (int i = 1; i <= k; ++i) {
cin >> a[i];
}
vector<LL> prime[25];
for (int i = 0; i < (1 << k); ++i) {
LL tmp = 1ll;
int cnt = 0, flag = 1;
for (int j = 0; j < k; ++j) {
if ((i & (1 << j)) > 0) {
if ((double)tmp > ((double)n / (double)a[j + 1])) {
flag = 0; break;
}
tmp = tmp * a[j + 1];
++cnt;
}
}
if (tmp <= n and flag) {
prime[cnt].push_back(tmp);
}
}
LL ans = 0;
for (int i = 1; i <= k; ++i) {
for (int j = 0; j < prime[i].size(); ++j) {
if (i % 2 == 1) {
ans += n / prime[i][j];
}
else {
ans -= n / prime[i][j];
}
}
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
求互质的对数,那么就要求这一对数不存在相同的质因子。但是检查这两个数是互质的,就需要检查这两个数的所有质因子都不相同,非常麻烦。所以我们可以换一个角度考虑:互质的对数等价于总对数-不互质的对数。
接下来求不互质的对数:朴素求法是,从左到右遍历整个序列,对于每一个数,前面有多少数和我有相同的质因子,就对不互质的对数的答案产生了多少贡献。然而,每一个数的质因子有很多,比如这个数是 30 30 30,包含3个质因子 2 , 3 , 5 2,3,5 2,3,5。那么这个位置对答案的贡献,是前面的数中包含 2 , 3 , 5 2,3,5 2,3,5的数的个数。由于有些数可能会包含其中的多个质因子,所以这里要用容斥原理来算出这个结果。
这道题的容斥原理用如下方法实现:我们令 c n t [ i ] cnt[i] cnt[i]表示对于一个质因子的集合 { p 1 , p 2 , ⋯ , p x } \{ p_1,p_2,\cdots,p_x \} {p1,p2,⋯,px},满足 p 1 p 2 ⋯ p x = i p_1p_2\cdots p_x=i p1p2⋯px=i,前面的数中完全包含这一组质因子的数的个数。根据容斥原理,我们要加上集合大小是奇数的数的贡献,减去集合大小是偶数的数的贡献,这样最后留下的就是正确的答案了。
#include
using namespace std;
typedef long long LL;
const int M = 1e6 + 5;
LL minp[M], cnt[M];
void getMinP() {
for (int i = 1; i <= M - 5; ++i) {
minp[i] = i;
}
for (int i = 2; i <= M - 5; ++i) {
if (minp[i] == i) {
for (int j = i + i; j <= M - 5; j += i) {
if (minp[j] == j) minp[j] = i;
}
}
}
}
LL ans = 0, n;
void main2() {
getMinP();
cin >> n;
for (int i = 1; i <= n; ++i) {
int x;
cin >> x;
vector<int> v;
while (x > 1) {
int y = minp[x];
v.push_back(y);
while (x % y == 0) {
x /= y;
}
}
for (int j = 1; j < (1 << (int)v.size()); ++j) {
LL tmp = 1, one = 0;
for (int k = 0; k < (int)v.size(); ++k) {
if ((j & (1 << k)) > 0) {
++one;
tmp *= v[k];
}
}
ans += ((LL)(one % 2 == 0) ? -1 : 1) * cnt[tmp];
++cnt[tmp];
}
}
cout << n * (n - 1) / 2 - ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
求解组合数的板子题。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
int n;
void main2() {
cin >> n;
for (int i = 1; i <= n; ++i) {
LL x, y;
cin >> x >> y;
cout << C(x, y) << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(1000000);
while (_--) main2();
return 0;
}
题目链接
统计出每一个出现过的字母的出现次数。设 m m m为字符总数。
从左到右遍历每一个字符,最初剩余位置是 r e s t = m rest=m rest=m,每一个字符都有 ( r e s t c n t i ) \binom{rest}{cnt_i} (cntirest)种选法,将其乘到答案中。其中 c n t i cnt_i cnti是这个字符的出现次数。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
int cnt[32];
string s;
void main2() {
cin >> s;
for (int i = 0; i < 26; ++i) {
cnt[i] = 0;
}
for (int i = 0; i < s.length(); ++i) {
++cnt[s[i] - 'a'];
}
LL m = s.length(), ans = 1;
for (int i = 0; i < 26; ++i) {
if (cnt[i] > 0) {
ans = ans * C(m, cnt[i]) % mod;
m -= cnt[i];
}
}
cout << ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(1000000);
while (_--) main2();
return 0;
}
题目链接
小球放盒子的模型,属于球相同,盒子不同,可以为空的类型。
假设有 n n n个盒子, m m m个球,球相同,盒子不同,盒子不可以为空,有多少种方案。不难想到隔板法,我们要把 m m m个完全一样的球分成 n n n组,也就是在 m m m个球的 m − 1 m-1 m−1个空隙里插入 n − 1 n-1 n−1个挡板,使之成为 n n n组。因为盒子不同,所以隔板法是成立的(如果盒子相同,那么两次分出相同的数量但是位置不一样的)。这时答案就是 ( m − 1 n − 1 ) \binom{m-1}{n-1} (n−1m−1)。
现在拓展到盒子可以为空,该怎么做呢?我们可以转换一下我们的问题,我们向原问题中加入 n n n个球,让每一个盒子都至少有一个球。那么现在就是问有 n n n个盒子, n + m n+m n+m个球,盒子不同,球相同,有多少种方案。不难看出答案就是 ( n + m − 1 n − 1 ) \binom{n+m-1}{n-1} (n−1n+m−1)。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
LL n, m;
void main2() {
cin >> n >> m;
cout << C(n + m - 1, n - 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(2000000);
while (_--) main2();
return 0;
}
题目链接
求错排数。令 n n n的错排数是 D n D_n Dn,有 D 1 = 0 , D 2 = 1 D_1=0,D_2=1 D1=0,D2=1,当 n > 2 n>2 n>2时,有 D n = ( n − 1 ) ( D n − 1 + D n − 2 ) D_n=(n-1)(D_{n-1}+D_{n-2}) Dn=(n−1)(Dn−1+Dn−2)。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL n;
LL d[1000005];
void main2() {
cin >> n;
d[1] = 0;
d[2] = 1;
for (int i = 3; i <= n; ++i) {
d[i] = (i - 1) * (d[i - 1] + d[i - 2]) % mod;
}
cout << d[n];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
经典的卡特兰数。有多少组括号,答案就是卡特兰数的第几个数。
卡特兰数通项公式: C n = ( 2 n n ) / ( n + 1 ) C_n=\binom{2n}{n} / (n+1) Cn=(n2n)/(n+1)。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
void main2() {
LL n;
cin >> n;
if (n % 2 == 1) {
cout << 0;
return;
}
cout << C(n, n / 2) * power(n / 2 + 1, mod - 2) % mod;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
init(1000000);
// cin >> _;
while (_--) main2();
return 0;
}
题目链接
双倍经验题目:HDU5184 - Brackets
有 n n n对括号的合法序列个数是 ( 2 n n ) n + 1 \frac{\binom{2n}{n}}{n+1} n+1(n2n),长度为 2 n 2n 2n的括号序列总个数是 ( 2 n n ) \binom{2n}{n} (n2n),所以非法括号序列的个数就是:
( 2 n n ) − ( 2 n n ) n + 1 = ( 2 n n ) × n n + 1 = ( 2 n ) ! n ! × n ! × n n + 1 = ( 2 n ) ! ( n − 1 ) ! ( n + 1 ) ! = ( 2 n n + 1 ) \binom{2n}{n}-\frac{\binom{2n}{n}}{n+1}=\binom{2n}{n} \times \frac{n}{n+1}=\frac{(2n)!}{n!\times n!}\times \frac{n}{n+1}=\frac{(2n)!}{(n-1)!(n+1)!}=\binom{2n}{n+1} (n2n)−n+1(n2n)=(n2n)×n+1n=n!×n!(2n)!×n+1n=(n−1)!(n+1)!(2n)!=(n+12n)
在小球放盒子的一些公式推导中,我们用到了这样的思想,即当想把公式从不允许空推到允许空的时候,可以给每一个盒子虚空放一个不存在的小球,然后带上这些小球,按照不允许空的公式算一个方案数,得到的结果就是答案。
这个题中,我们假设题目所给的前缀串中左括号个数为 x x x,右括号个数为 y y y,那么不难看出,还剩下 n − x n-x n−x个左括号和 n − y n-y n−y个右括号待填。这些可以演变成 n − x n-x n−x对括号和 x − y x-y x−y个右括号待填。我们想求填完这些后整个串合法的填充方案数,可以转化成总共的方案数-非法方案数。
当我们只考虑 n − x n-x n−x对括号的时候,不合法的方案数个数是 ( 2 ( n − x ) ( n − x ) + 1 ) \binom{2(n-x)}{(n-x)+1} ((n−x)+12(n−x)),然后按照不允许空到允许空的思想,我们现在还剩下 x − y x-y x−y个右括号等待填充,可以认为剩下的 x − y x-y x−y个空位就是空盒子, x − y x-y x−y个右括号就是虚构出来的小球,那么非法方案数就是 ( 2 ( n − x ) + x − y 2 ( n − x ) + 1 + x − y ) = ( 2 n − x − y n − y + 1 ) \binom{2(n-x)+x-y}{2(n-x)+1+x-y}=\binom{2n-x-y}{n-y+1} (2(n−x)+1+x−y2(n−x)+x−y)=(n−y+12n−x−y)。
最后的答案 a n s = ( 2 n − x − y n − x ) − ( 2 n − x − y n − y + 1 ) ans=\binom{2n-x-y}{n-x}-\binom{2n-x-y}{n-y+1} ans=(n−x2n−x−y)−(n−y+12n−x−y)。
注意要特判给定的前缀不合法和长度不合法的情况。
#include
using namespace std;
typedef long long LL;
const LL mod = 1e9 + 7;
LL jc[2000050], inv[2000050];
template <class T> T power(T a, LL b) {
T res = 1;
for (; b; b >>= 1) {
if (b % 2) res = (res * a) % mod;
a = (a * a) % mod;
}
return res;
}
void init(LL iN) {
jc[0] = 1;
for (LL i = 1; i <= iN + 1; ++i) {
jc[i] = (jc[i - 1] * i) % mod;
}
inv[iN + 1] = power(jc[iN + 1], mod - 2);
for (LL i = iN; i >= 0; --i) {
inv[i] = inv[i + 1] * (i + 1) % mod;
}
}
LL C(LL N, LL M) {
return jc[N] * inv[M] % mod * inv[N - M] % mod;
}
LL n, x = 0, y = 0;
string s;
void main2() {
cin >> n >> s;
if (n % 2 == 1) {
cout << 0;
return;
}
for (char c: s) {
if (c == '(') ++x;
else ++y;
if (x < y) {
cout << 0;
return;
}
}
if (y > x) {
cout << 0;
return;
}
if (x > n / 2) {
cout << 0;
return;
}
n >>= 1;
cout << ((C(n * 2 - x - y, n - x) - C(n * 2 - x - y, n - x - 1)) % mod + mod) % mod;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL _ = 1;
// cin >> _;
init(1000000);
while (_--) main2();
return 0;
}