【做题笔记】CSES-Mathematics

CSES2164 - Josephus Queries

题目链接

不难发现,当人数是奇数和偶数时,情况稍有不同。先看简单的偶数情况:

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) i4(i>n)步。假设现在有一个子问题,问有1 2 3 4,要走 i − 4 i-4 i4步,删掉的数是哪个,假设这个问题的答案是 a n s ans ans,我们可以通过 a n s ans ans求出原问题的答案是 2 a n s − 1 2ans - 1 2ans1。这样不断递归寻找子问题的答案再原路返回,就可以求出这个原问题的答案了。我们发现,每次递归,数列中的数都会除以 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<n) i(2i<n)步,那么答案就是 2 i 2i 2i;假设求走 ( n + 1 ) / 2 (n+1)/2 (n+1)/2步,这个时候正好落在 1 1 1处,答案就是 1 1 1。至于剩下的,我们可以设立子问题。假设走了 ( n + 1 ) / 2 = 4 (n+1)/2=4 (n+1)/2=4步,那么剩余的数列是3 5 7,子问题就设置为 n = 3 n=3 n=3,求删的第 i − 4 i-4 i4个人是第几号,求得答案 a n s ans ans。不难发现,原问题的答案是 2 a n s + 1 2ans+1 2ans+1

如果当前的问题可以直接一轮求得结果,就不需要再向下递归求解子问题了,可以直接返回结果。

#include<bits/stdc++.h>
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;
}

CSES1095 - Exponentiation

题目链接

快速幂求解即可。

#include<bits/stdc++.h>
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;
}

CSES1712 - Exponentiation II

题目链接

由于我们的模数是一个质数,设模数为 m = 1 0 9 + 7 m=10^9+7 m=109+7,那么 φ ( m ) = m − 1 \varphi (m)=m-1 φ(m)=m1

根据扩展欧拉定理, a b ≡ a b m o d    φ ( m ) a^b \equiv a^{b\mod \varphi(m)} ababmodφ(m)。所以套两层快速幂即可。

#include<bits/stdc++.h>
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;
}

CSES1713 - Counting Divisors

题目链接

有一种在调和级数时间复杂度内找到 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<bits/stdc++.h>
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;
}

CSES1081 - Common Divisors

题目链接

有一种最后一个点会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<bits/stdc++.h>
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;
}

CSES1082 - Sum of Divisors

题目链接

数据范围达到 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 ini+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<bits/stdc++.h>
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;
}

CSES2182 - Divisor Analysis

题目链接

这道题有三个小问,我们一一来看。

第一个是要求因子的数量。我们得到的数是质因数分解的形式,所以我们可以考虑把每一个质因数当作一个物品来看待,我们对于每一个质因数都有 b i b_i bi种选法,将这 n n n个质因数的每一个的取法种数乘到一起,就是可以取的因子的种数。

第二个是求因子的和。我们可以从前往后逐个考虑质因子。在考虑第 i i i个质因子的时候,我们考虑如何从前 i − 1 i-1 i1个质因子的结果中转移过来。我们设由前 i i i个质因子构成的所有因子和答案是 S i S_i Si,且令 S 0 = 1 S_0=1 S0=1。考虑前 i − 1 i-1 i1个质因子所构成的所有因子的和 S i − 1 S_{i-1} Si1,当前第 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=Si1×(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=Si1×1ai1aibi

第三个是求因子的积。我们同样从前往后逐个考虑质因子。在考虑第 i i i个质因子的时候,我们考虑如何从前 i − 1 i-1 i1个质因子的结果中转移过来。我们设由前 i i i个质因子构成的所有因子积的答案是 P i P_i Pi,且令 P 0 = 1 P_0=1 P0=1。考虑前 i − 1 i-1 i1个质因子所构成的所有因子的积是 P i − 1 P_{i-1} Pi1,当前第 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} Pi11+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 i1个质因子所组成的所有因子的个数次。而这个因子的个数,是 ( 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)(bi1+1)。每次求完 P i P_i Pi后,更新一下这个因子的个数即可。设前 i − 1 i-1 i1个质因子所组成的因子个数是 C i − 1 C_{i-1} Ci1,那么 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=Pi11+bi×(1×ai×ai2aibi)Ci1,而整个括号里面的底数相同,幂数可以用等差数列求个和,于是式子变成 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=Pi11+bi×(ai(1+bi)bi/2)Ci1

在用快速幂的时候要注意,根据费马小定理,幂数取模时要对 m o d − 1 mod-1 mod1取模(因为满足 m o d mod mod是个质数)。

#include<bits/stdc++.h>
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;
}

CSES2185 - Prime Multiples

题目链接

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<bits/stdc++.h>
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;
}

CSES2417 - Counting Coprime Pairs

题目链接

求互质的对数,那么就要求这一对数不存在相同的质因子。但是检查这两个数是互质的,就需要检查这两个数的所有质因子都不相同,非常麻烦。所以我们可以换一个角度考虑:互质的对数等价于总对数-不互质的对数。

接下来求不互质的对数:朴素求法是,从左到右遍历整个序列,对于每一个数,前面有多少数和我有相同的质因子,就对不互质的对数的答案产生了多少贡献。然而,每一个数的质因子有很多,比如这个数是 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 p1p2px=i,前面的数中完全包含这一组质因子的数的个数。根据容斥原理,我们要加上集合大小是奇数的数的贡献,减去集合大小是偶数的数的贡献,这样最后留下的就是正确的答案了。

#include<bits/stdc++.h>
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;
}

CSES1079 - Binomial Coefficients

题目链接

求解组合数的板子题。

#include<bits/stdc++.h>
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;
}

CSES1715 - Creating Strings II

题目链接

统计出每一个出现过的字母的出现次数。设 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<bits/stdc++.h>
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;
}

CSES1716 - Distributing Apples

题目链接

小球放盒子的模型,属于球相同,盒子不同,可以为空的类型。

假设有 n n n个盒子, m m m个球,球相同,盒子不同,盒子不可以为空,有多少种方案。不难想到隔板法,我们要把 m m m个完全一样的球分成 n n n组,也就是在 m m m个球的 m − 1 m-1 m1个空隙里插入 n − 1 n-1 n1个挡板,使之成为 n n n组。因为盒子不同,所以隔板法是成立的(如果盒子相同,那么两次分出相同的数量但是位置不一样的)。这时答案就是 ( m − 1 n − 1 ) \binom{m-1}{n-1} (n1m1)

现在拓展到盒子可以为空,该怎么做呢?我们可以转换一下我们的问题,我们向原问题中加入 n n n个球,让每一个盒子都至少有一个球。那么现在就是问有 n n n个盒子, n + m n+m n+m个球,盒子不同,球相同,有多少种方案。不难看出答案就是 ( n + m − 1 n − 1 ) \binom{n+m-1}{n-1} (n1n+m1)

#include<bits/stdc++.h>
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;
}

CSES1717 - Christmas Party

题目链接

求错排数。令 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=(n1)(Dn1+Dn2)

#include<bits/stdc++.h>
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;
}

CSES2064 - Bracket Sequences I

题目链接

经典的卡特兰数。有多少组括号,答案就是卡特兰数的第几个数。

卡特兰数通项公式: C n = ( 2 n n ) / ( n + 1 ) C_n=\binom{2n}{n} / (n+1) Cn=(n2n)/(n+1)

#include<bits/stdc++.h>
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;
}

CSES2187 - Bracket Sequences II

题目链接

双倍经验题目: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=(n1)!(n+1)!(2n)!=(n+12n)

在小球放盒子的一些公式推导中,我们用到了这样的思想,即当想把公式从不允许空推到允许空的时候,可以给每一个盒子虚空放一个不存在的小球,然后带上这些小球,按照不允许空的公式算一个方案数,得到的结果就是答案。

这个题中,我们假设题目所给的前缀串中左括号个数为 x x x,右括号个数为 y y y,那么不难看出,还剩下 n − x n-x nx个左括号和 n − y n-y ny个右括号待填。这些可以演变成 n − x n-x nx对括号和 x − y x-y xy个右括号待填。我们想求填完这些后整个串合法的填充方案数,可以转化成总共的方案数-非法方案数。

当我们只考虑 n − x n-x nx对括号的时候,不合法的方案数个数是 ( 2 ( n − x ) ( n − x ) + 1 ) \binom{2(n-x)}{(n-x)+1} ((nx)+12(nx)),然后按照不允许空到允许空的思想,我们现在还剩下 x − y x-y xy个右括号等待填充,可以认为剩下的 x − y x-y xy个空位就是空盒子, x − y x-y xy个右括号就是虚构出来的小球,那么非法方案数就是 ( 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(nx)+1+xy2(nx)+xy)=(ny+12nxy)

最后的答案 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=(nx2nxy)(ny+12nxy)

注意要特判给定的前缀不合法和长度不合法的情况。

#include<bits/stdc++.h>
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;
}

猜你喜欢

转载自blog.csdn.net/xhyu61/article/details/126813926