原文摘自:http://www.cppblog.com/vici/archive/2011/09/05/155103.html
据文章说,原文是俄文的,各种翻译才成为中文。表示膜拜,仰慕。ORZ。就需要这么屌屌哒人。。
容斥原理的具体证明就不在描述了。。
这应该是对容斥原理最简单粗暴的解释了。。
我们这篇文章关键解释了几个有关容斥原理的小小问题。
1.一个简单的排列问题。
由0到9的数字组成排列,要求第一个数大于1,最后一个小于8,问,共有多少种排列?
分析:直接求的话,求起来很复杂。那么我们可以求该排列的逆问题。也就是求第一数<=1,或者最后一位数>=8的排列数。
我们假设A为第一个数<=1,B为最后一个数>=8。
那么我们要求的逆问题的排列数就是A + B - (A & B)。(我们定义‘+’为集合的并,&为集合的交集,数学符号和逻辑符号并用,望谅解)。。
为什么药减去A & B呢?因为我们的A,也就是说第一个数<=1中也有满足最后一个数>=8的情况,同样的最后一个数>=8的情况中也有第一个数<=1的,那么我们A+B就会多加一部分第一个数<=1&&最后一个数>=8,减去的就是这一部分。。
C = A + B - (A & B) = 2 * 9! + 2 * 9! - 2 * 2 * 8!。那么这部分就是第一位<=1,或者最后一个>=8的排列数。。
我们用10! - C就是我们要求的,第一个数大于1,最后一个数小于8的排列数。。
2.(0, 1, 2)序列问题。
长度为n,由0,2,1组成序列,每个数最少出现一次,这样的序列有多少种。
分析:我们发现,直接求解的话也是比较复杂的。。那么我们就可以求解他是逆问题。
也就是不全部出现0,1,2的序列会有多少种。。用原文是话就是,不出现某些数的序列的种数。
我们设A0是不出现0的序列的个数,A1是不出现1的序列的个数,A2是不出现2的序列的个数。
A0 + A1 + A2 - (A0 & A1) - (A0 & A2) - (A1 & A2) + (A0 & A1 & A2)。就是我们所要求的逆问题是序列。看下面的维恩图:
画的有点挫(不是有点,是太挫了。。。。 - -)。。
可以看来我们可以用另一个问题来解释了。。这个问题是维恩图与求同时不能被三个数整除的一样了。。其实差不多。。
C = A0 + A1 + A2 - (A0 & A1) - (A0 & A2) - (A1 & A2) + (A0 & A1 & A2) = 2^n + 2^n + 2^n - 1 - 1 - 1 + 0.。这就是序列中不出现某些数序列的种类数。。。
总共是序列众数 3^n - (3 * 2^n - 3 + 0)。。就是我们所求的每个数都最少出现一次的序列数。。
3.方程整数解问题。
给一个方程 x1 + x2 + x3 + x4 + x5 + x6 = 20, 0 <= xi <= 8。。问这样的整数解有多少种。。
4.求指定区间内与n互素的数有多少个。。
给定r,n 求[1,r]内与n互素的个数有多少个?看到这个问题,数论有学一点的童鞋可能会想如果r = n的话,不就是欧拉函数了吗?是的,可惜这个问题的r ,n是不一定会相等的。。
分析:直接求解问题就是比较复杂的。所以我们还是研究这个问题是逆问题。也就是说求gcd(k,n) >= 2,在1 - n 之间k有多少个 。
那么我们就可以枚举n的素因子来进行求解。。。
Code:小小代码:
//时间复杂度O(sqrt(n))...
int solve(int r, int n)
{
int p[N], top = 0, ans;
for(int i = 2; i * i <= n; i ++){
if(n % i == 0){
p[top ++] = i;
while(n % i == 0) n = n / i;
}
}
if(n > 1) p[top ++] = n;
//枚举子集来进行判断加减,cnt为子集元素个数
for(int i = 1; i < (i << top); i ++){
int cnt = 0, tmp = 1;
for(int j = 0; j < top; j ++){
cnt ++;
tmp = tmp * p[j];
}
}
if(cnt % 2) ans += r / tmp;
else ans -= r / tmp;
return r - ans;
}
5.在给定区间内被给定集合中至少一个数整除的个数。。
这个问题恰好和hdu 1796 一样。。详见博客:http://blog.csdn.net/u011394362/article/details/40791385
6.能满足一定数目匹配的字符串个数问题。
7.路径数目问题。
8.素数四元组问题。
给你n个数