定义:在计数时,必须注意没有重复,没有遗漏。为了使重叠部分不被重复计算,人们研究出一种新的计数方法。
这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复。
这种计数的方法称为容斥原理。
原理好懂,难的是应用
n封信对应n个信封,求恰好全部装错了信封的方案数
全错排问题,可以用容斥写
首先,所有装信的总数是n!(在n中任选一个信封放进一封信,然后在剩下的n-1中任选一个信封放进一封信,以此类推,所以是n*(n-1)*(n-2)... = n!)
假设
A1表示1封信装对信封,数量是(n-1)! (只有n-1个位置可以乱放)
A2表示2封信装对信封,数量是(n-2)! (只有n-2个位置可以乱放)
......
An表示n封信装对信封,数量是1
那么这题的答案就是
n! - C(n, 1)*|A1| + C(n, 2)*|A2| - C(n, 3)*|A3| + ... + (-1)^n * C(n, n)*|An|
把C(n, m)用组合数公式代入式子化简得: n! - n! / 1! + n! / 2! - n! / 3! + ... + (-1)^n * n! / n!
提取n!得: n!(1 - 1/1! + 1/2! - 1/3! + ... + (-1)^n * 1/n!)
#include<cstdio>
typedef long long LL;
int n, flag;
LL fac[25];
LL ans;
void init(){
fac[0] = 1;
for(int i = 1; i <= 20; i ++) fac[i] = fac[i-1] * i;
}
int main(){
init();
while(~scanf("%d", &n)){
ans = fac[n];
flag = -1;//容斥的符号变化
for(int i = 1; i <= n; i ++){
ans += flag * fac[n] / fac[i];
flag = -flag;
}
printf("%I64d\n", ans);
}
}
全错排好像有个专门的公式,不怎么会,粘个代码:
#include<cstdio>
typedef long long LL;
int n;
LL dp[25];
void init(){
dp[1] = 0;
dp[2] = 1;
for(int i = 3; i <= 20; i ++){
dp[i] = (i-1) * (dp[i-1] + dp[i-2]);
}
}
int main(){
init();
while(~scanf("%d", &n)){
printf("%I64d\n", dp[n]);
}
}
给n盆花涂色,从m种颜色中选取k种颜色涂,保证正好用上k种颜色,你必须用上这k种颜色去涂满n个相邻的花,并且要求相邻花的颜色不同,求方案数。(1 ≤ n, m ≤ 1e9 , 1 ≤ k ≤ 1e6 , k ≤ n, m)
首先,用k种颜色涂花,假如不考虑全部用上,那么总的方案数是多少
第一盆花有k种颜色选择,之后的花因为不能跟前一盆花的颜色相同,所以有k-1种选择
于是总方案数为k*(k-1)^(n-1)
因为题目问必须用上k种颜色
这里面包含了只用k-1种颜色的情况,应该减掉所有用k-1种的情况
减掉的东西里面,这里面包含了只用k-2种颜色的情况,应该加回来
...
反反复复,最后就得出答案了
最后答案就是
C(m,k) * ( k * (k-1)^(n-1) + [∑((-1)^i * C(k, k - i) * (k-i) * (k-i-1)^(n-1)) ] ) (1 <= i <= k-1)
#include<cstdio>
typedef long long LL;
const int N = 1000000 + 5;
const int MOD = (int)1e9 + 7;
int F[N], Finv[N], inv[N];
LL pow_mod(LL a, LL b, LL p){
LL ret = 1;
while(b){
if(b & 1) ret = (ret * a) % p;
a = (a * a) % p;
b >>= 1;
}
return ret;
}
void init(){
inv[1] = 1;
for(int i = 2; i < N; i ++){
inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
}
F[0] = Finv[0] = 1;
for(int i = 1; i < N; i ++){
F[i] = F[i-1] * 1ll * i % MOD;
Finv[i] = Finv[i-1] * 1ll * inv[i] % MOD;
}
}
int comb(int n, int m){
if(m < 0 || m > n) return 0;
return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD;
}
int main(){
init();
int T, n, m, k, ans, flag, temp;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++){
scanf("%d%d%d", &n, &m, &k);
ans = k * pow_mod(k-1, n-1, MOD) % MOD;
flag = -1;
//计算容斥
for(int i = 1; i <= k-1; i ++){
ans = (ans + 1ll * flag * comb(k, k-i) * (k-i) % MOD * pow_mod((k-i-1), n-1, MOD) % MOD) % MOD;
flag = -flag;
}
//接下来计算C(m, k)
temp = Finv[k];
for(int i = 1; i <= k; i ++){
temp = 1ll * temp * (m-k+i) % MOD;
}
ans = ((1ll * ans * temp) % MOD + MOD) % MOD;
printf("Case #%d: %d\n", cas, ans);
}
}
求(a,b)区间与n互质的数的个数.
我们可以先求(1~b)区间的答案,然后减去(1~a-1)区间的答案
这样问题就转换为(1~m)区间与n互质的数的个数
互质的不好求,我们可以求不互质的个数,然后减一下
所以我们先求出n的所有质因数,然后用容斥做
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
vector <LL > prime_factor;
vector <LL > vec;
void init(LL x){
//预处理质因子
prime_factor.clear();
for(LL i = 2; i*i <= x; i++){
if(x % i == 0){
prime_factor.push_back(i);
while(x % i == 0) x /= i;
}
}
if(x > 1) prime_factor.push_back(x);
//预处理容斥中的倍数项,符号正好是一个减一个加
int vec_size;
vec.clear();
for(int i = 0; i < prime_factor.size(); i ++){
vec_size = vec.size();//因为vec.size()在接下来的运算中会改变
for(int j = 0; j < vec_size; j ++){
vec.push_back(vec[j] * prime_factor[i]);
}
vec.push_back(prime_factor[i]);
}
}
LL work(LL x){
//接下来容斥
LL ans = x, flag = -1;
for(int i = 0; i < vec.size(); i ++){
ans += flag * x / vec[i];
flag = -flag;
}
return ans;
}
int main(){
int T;
LL l, r, n;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++){
scanf("%I64d%I64d%I64d", &l, &r, &n);
init(n);
printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));
}
}
样例理解:比如30 = 2 * 3 * 5
一开始数组里面什么都没有
然后变成
2
然后把3挨个乘过去的值放在数组后面,同时将自己也放进数组
2 6 3
然后5也是一样
2 6 3 10 30 15 5
最后答案n就是等于
n - n / 2 + n / 6 - n / 3 + n / 10 - n / 30 + n / 15 - n / 5
位运算方法:
#include<cstdio>
#include<vector>
using namespace std;
typedef long long LL;
vector <LL > prime_factor;
void init(LL x){
//预处理质因子
prime_factor.clear();
for(LL i = 2; i*i <= x; i++){
if(x % i == 0){
prime_factor.push_back(i);
while(x % i == 0) x /= i;
}
}
if(x > 1) prime_factor.push_back(x);
}
LL work(LL x){
//接下来容斥
LL ans = x, cnt, temp;
for(int i = 1; i < (1 << prime_factor.size()); i ++){
cnt = 0;
temp = 1;
for(int j = 0; j < prime_factor.size(); j ++){
if(i & (1 << j)){
temp *= prime_factor[j];
cnt ++;
}
}
if(cnt & 1) ans -= x / temp;
else ans += x / temp;
}
return ans;
}
int main(){
int T;
LL l, r, n;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++){
scanf("%I64d%I64d%I64d", &l, &r, &n);
init(n);
printf("Case #%d: %I64d\n", cas, work(r) - work(l-1));
}
}