目录
一,容斥原理
1,原理
2,模板
template<typename T=long long>
class InclusionExclusion {
public:
T getSum(int low, int high) {
T ans = 0, flag = 1;
for (int i = low; i <= high; i++) {
ans += getValue(i)*flag;
flag *= -1;
}
return ans;
}
protected:
virtual T getValue(int key) {
return 0;
}
};
3,常见应用
(1)在n个球中可重复的选s次,求其中b个球至少被选中一次的情况数
公式:
class IECombina:public InclusionExclusion<> {
public:
long long getSumNum(int n, int s, int b) {
this->n = n, this->s = s, this->b = b;
return getSum(0, b);
}
private:
long long getValue(int key) {
return Multi::multiMulti(n - key, s, p) * ComNum::getValue(b, key, p) % p;
}
int n, s, b;
vector<long long>comninaNum;
int p = 1000000007;
};
(2)在n个球中可重复的选s次,每相邻k次之中不能有重复的球,求其中每个球至少被选中一次的情况数
公式:
class IECombina2 :public InclusionExclusion<> {
public:
long long getSumNum(int n, int s, int k) {
this->n = n, this->s = s, this->k = k;
long long ans = (getSum(0, n-k) % p + p) % p;
if (n % 2 != k % 2)ans = p - ans;
for (int i = 0; i < k; i++)ans = ans * (n - i) % p;
return ans % p;
}
private:
long long getValue(int key) {
return Multi::multiMulti(key, s - k, p) * ComNum::getValue(n - k, key, p) % p;
}
int n, s, k;
int p = 1000000007;
};
(3)从1到n有多少数是v中任一数的倍数
class IECombina3 :public InclusionExclusion<> {
public:
long long getSumNum(const vector<int>& v, long long n) { //从1到n有多少数是v中任一数的倍数
m.clear();
for (int i = 1; i < (1 << v.size()); i++) {
long long lcms = 1;
int s = 0;
for (int j = 0; j < v.size(); j++) {
if (i & (1 << j))s++, lcms = lcm(lcms, v[j]);
if (lcms > n || lcms == 0)break;
}
m[s] += n / lcms;
}
return getSum(1, v.size());
}
private:
long long getValue(int key) {
return m[key];
}
//最大公约数
static long long gcd(long long a, long long b)
{
if (b == 0)return a;
return gcd(b, a % b);
}
//最小公倍数
static long long lcm(long long a, long long b)
{
a /= gcd(a, b);
if (a > LLONG_MAX / b)return 0;
return a * b;
}
private:
map<int, long long>m;
};
二,OJ实战(模板)
力扣 920. 播放列表的数量
你的音乐播放器里有 n
首不同的歌,在旅途中,你计划听 goal
首歌(不一定不同,即,允许歌曲重复)。你将会按如下规则创建播放列表:
- 每首歌 至少播放一次 。
- 一首歌只有在其他
k
首歌播放完之后才能再次播放。
给你 n
、goal
和 k
,返回可以满足要求的播放列表的数量。由于答案可能非常大,请返回对 109 + 7
取余 的结果。
示例 1:
输入:n = 3, goal = 3, k = 1 输出:6 解释:有 6 种可能的播放列表。[1, 2, 3],[1, 3, 2],[2, 1, 3],[2, 3, 1],[3, 1, 2],[3, 2, 1] 。
示例 2:
输入:n = 2, goal = 3, k = 0 输出:6 解释:有 6 种可能的播放列表。[1, 1, 2],[1, 2, 1],[2, 1, 1],[2, 2, 1],[2, 1, 2],[1, 2, 2] 。
示例 3:
输入:n = 2, goal = 3, k = 1 输出:2 解释:有 2 种可能的播放列表。[1, 2, 1],[2, 1, 2] 。
提示:
0 <= k < n <= goal <= 100
class Solution {
public:
int numMusicPlaylists(int n, int goal, int k) {
return IECombina2().getSumNum(n, goal, k);
}
};
力扣 2928. 给小朋友们分糖果 I
给你两个正整数 n
和 limit
。
请你将 n
颗糖果分给 3
位小朋友,确保没有任何小朋友得到超过 limit
颗糖果,请你返回满足此条件下的 总方案数 。
示例 1:
输入:n = 5, limit = 2 输出:3 解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。
示例 2:
输入:n = 3, limit = 3 输出:10 解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。
提示:
1 <= n <= 50
1 <= limit <= 50
class Solution :public InclusionExclusion<>, public ComNum<> {
public:
int distributeCandies(int n, int limit) {
this->n = n, this->k = limit;
return getSum(0, 3);
}
long long getValue(int key) {
int s = n + 2 - key * (k + 1);
return s >= 2 ? getComNum(3, key) * getComNum(s, 2) : 0;
}
int n, k;
};
力扣 2929. 给小朋友们分糖果 II
给你两个正整数 n
和 limit
。
请你将 n
颗糖果分给 3
位小朋友,确保没有任何小朋友得到超过 limit
颗糖果,请你返回满足此条件下的 总方案数 。
示例 1:
输入:n = 5, limit = 2 输出:3 解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。
示例 2:
输入:n = 3, limit = 3 输出:10 解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。
提示:
1 <= n <= 106
1 <= limit <= 106
思路:
题目和2928. 给小朋友们分糖果 I是一样的,只是数字范围变了。
这里的组合数刚好超过了int又没有超过long long,所有直接求值,不能mod p,我的模板是mod p的,这里就干脆改成手动计算组合数。
class Solution :public InclusionExclusion<>, public ComNum<> {
public:
long long distributeCandies(int n, int limit) {
this->n = n, this->k = limit;
return getSum(0, 3);
}
long long getValue(int key) {
long long s = n + 2 - key * (k + 1);
long long C3key = key%3?3:1;
long long Cs2 = s*(s-1)/2;
return s >= 2 ? C3key * Cs2 : 0;
}
int n, k;
};
力扣 3116. 单面值组合的第 K 小金额
给你一个整数数组 coins
表示不同面额的硬币,另给你一个整数 k
。
你有无限量的每种面额的硬币。但是,你 不能 组合使用不同面额的硬币。
返回使用这些硬币能制造的 第 kth
小 金额。
示例 1:
输入: coins = [3,6,9], k = 3
输出: 9
解释:给定的硬币可以制造以下金额:
3元硬币产生3的倍数:3, 6, 9, 12, 15等。
6元硬币产生6的倍数:6, 12, 18, 24等。
9元硬币产生9的倍数:9, 18, 27, 36等。
所有硬币合起来可以产生:3, 6, 9, 12, 15等。
示例 2:
输入:coins = [5,2], k = 7
输出:12
解释:给定的硬币可以制造以下金额:
5元硬币产生5的倍数:5, 10, 15, 20等。
2元硬币产生2的倍数:2, 4, 6, 8, 10, 12等。
所有硬币合起来可以产生:2, 4, 5, 6, 8, 10, 12, 14, 15等。
提示:
1 <= coins.length <= 15
1 <= coins[i] <= 25
1 <= k <= 2 * 109
coins
包含两两不同的整数。
class Solution :public Bsearch<long long>, public IECombina3 {
public:
long long findKthSmallest(vector<int>& coins, int k) {
this->coins = coins, this->k = k;
return find(1, coins[0] * this->k);
}
private:
bool isOk(long long x) //若isOk(x)且!isOk(y)则必有y<x
{
return getSumNum(coins, x) >= k;
}
vector<int> coins;
long long k;
};
三,OJ实战(非模板)
CSU 2140 Rikka's function
题目:
思路:
代码:
#include<iostream>
using namespace std;
int p = 1000000007;
long long f[1000005];
long long get_mi(long long n, int k)
{
if (k == 0)return 1;
long long r = get_mi(n, k / 2) % p;
r = (r*r) % p;
if (k % 2)r = (r*n) % p;
return r;
}
int main()
{
int n, m;
cin >> n >> m;
f[m] = m, f[m + 1] = 1;
for (int i = m - 1; i >= 1; i--)f[i] = f[i + 1] * i%p;
long long s = 0, t = get_mi(f[1], p - 2) % p;
for (int i = 0; i <= m; i++)
{
long long r = t;
if (i % 2)r *= -1;
r = r*f[i+1] % p;
r = r*f[m-i+1] % p;
r = r*get_mi(m - i, n) % p;
s += r;
}
s = s%p + p;
cout << s%p << endl;
return 0;
}
CSU 1803 2016
题目:
Description
给出正整数 n 和 m,统计满足以下条件的正整数对 (a,b) 的数量:
1. 1≤a≤n,1≤b≤m;
2. a×b 是 2016 的倍数。
Input
输入包含不超过 30 组数据。
每组数据包含两个整数 n,m (1≤n,m≤10 9).
Output
对于每组数据,输出一个整数表示满足条件的数量。
Sample Input
32 63
2016 2016
1000000000 1000000000
Sample Output
1
30576
7523146895502644
这个题目直接枚举g=gcd(a,2016)即可
2016=2*2*2*2*2*3*3*7,所以g有6*3*2=36种情况。
对于每个g,求a满足gcd(a,2016)恰好是g,求b使得2016| a*b
一,求a满足gcd(a,2016)恰好是g
即求1,2,3......n/g这些数中,有多少个和2016/g互质,用容斥原理即可
二,求b使得2016| a*b
即求1,2,3......m这些数中,有多少个是2016/g的倍数,答案就是m*g/2016
代码:
#include<iostream>
using namespace std;
int n, m;
long long f(int g)
{
int nn = n / g;
long long s = nn - (1008 % g == 0)*nn / 2 - (672 % g == 0)*nn / 3 - (288 % g == 0)*nn / 7;
s += (336 % g == 0)*nn / 6 + (96 % g == 0)*nn / 21 + (144 % g == 0)*nn / 14 - (48 % g == 0)*nn / 42;
long long gg = g;
return gg*m / 2016 * s;
}
long long f7(int g)
{
return f(g) + f(g * 7);
}
long long f5(int g)
{
return f7(g) + f7(g * 3) + f7(g * 9);
}
int main()
{
while (cin >> n >> m)cout << f5(1) + f5(2) + f5(4) + f5(8) + f5(16) + f5(32) << endl;
return 0;
}
UVA 10325 The Lottery
题目:
用了状态压缩来枚举2^m种状态,用k对应状态。
注意一个细节,当r超过n时,r/n就已经是0了,就不要再继续求最小公倍数了,否则会越界,因为多个数的最小公倍数很容易超过long long的范围。
代码:
#include<iostream>
using namespace std;
int gcd(int a, int b)
{
if (b)return gcd(b, a%b);
return a;
}
int main()
{
int n, m, l[15], s;
while (cin >> n >> m)
{
s = 0;
for (int i = 0; i < m; i++)cin >> l[i];
for (int k = 0; k < (1 << m); k++)
{
long long r = 1, b = 0;
for (int i = 0; i < m; i++)if (k&(1 << i))
{
if (r <= n)r *= l[i] / gcd(l[i], r);
b++;
}
if (b % 2)s -= n/r;
else s += n/r;
}
cout << s << endl;
}
return 0;
}
力扣 2930. 重新排列后包含指定子字符串的字符串数目
给你一个整数 n
。
如果一个字符串 s
只包含小写英文字母,且 将 s
的字符重新排列后,新字符串包含 子字符串 "leet"
,那么我们称字符串 s
是一个 好 字符串。
比方说:
- 字符串
"lteer"
是好字符串,因为重新排列后可以得到"leetr"
。 "letl"
不是好字符串,因为无法重新排列并得到子字符串"leet"
。
请你返回长度为 n
的好字符串 总 数目。
由于答案可能很大,将答案对 109 + 7
取余 后返回。
子字符串 是一个字符串中一段连续的字符序列。
示例 1:
输入:n = 4 输出:12 解释:总共有 12 个字符串重新排列后包含子字符串 "leet" :"eelt" ,"eetl" ,"elet" ,"elte" ,"etel" ,"etle" ,"leet" ,"lete" ,"ltee" ,"teel" ,"tele" 和 "tlee" 。
示例 2:
输入:n = 10 输出:83943898 解释:长度为 10 的字符串重新排列后包含子字符串 "leet" 的方案数为 526083947580 。所以答案为 526083947580 % (109 + 7) = 83943898 。
提示:
1 <= n <= 105
class Solution {
public:
int stringCount(int n) {
int p=1000000007;
long long mi23=Multi::multiMulti(23,n-1,p);
long long mi24=Multi::multiMulti(24,n-1,p);
long long mi25=Multi::multiMulti(25,n-1,p);
long long mi26=Multi::multiMulti(26,n-1,p);
long long ans=mi25-mi24*2+mi23;
mi23*=23,mi24*=24,mi25*=25,mi26*=26;
return ((mi26-mi25*3+mi24*3-mi23-n*ans)%p+p)%p;
}
};