容斥原理我初中就听老师说过了,不知道你们有没有听过(/≧▽≦)/
百度百科说:
在计数时,必须注意没有重复,没有遗漏。
为了使重叠部分不被重复计算,人们研究出一种新的计数方法。
这种方法的基本思想是:先不考虑重叠的情况,把包含于某内容中的所有对象的数目先计算出来,然后再把计数时重复计算的数目排斥出去,使得计算的结果既无遗漏又无重复。
这种计数的方法称为容斥原理。
好标准的说法(#-.-)
那我举个简单的例子
两个集合的容斥原理: 设A, B是两个有限集合
那么
|A + B| = |A| + |B| - |AB|
|A|表示A集合中的元素个数
三个集合的容斥原理: 设A, B, C是三个有限集合
那么
|A + B + C| = |A| + |B| + |C| - |AB| - |AC| - |BC| + |ABC|
这就叫容斥原理
接下来直接做例题了
全错排(装错信封问题)
hdu 1465
http://acm.hdu.edu.cn/showproblem.php?pid=1465
n封信对应n个信封
求恰好全部装错了信封的方案数
本来全错排是有自己的一个公式的,叫全错排公式(跟容斥没关系)
那我顺便来讲讲全错排( >ω<)
要装第i封信的时候,先把前i-1个信全装错信封,然后随便选其中一个与第i封信交换,有i-1种选法
那么dp[i] = (i-1) * dp[i-1]
但是还有一种情况
要装第i封信的时候,先从i-1封信中任选i-2个信把他们全装错信封,然后把剩下的那个信与第i个交换,从i-1封信中任选i-2个信有i-1种选法
那么dp[i] = (i-1) * dp[i-2]
两个式子联合起来
就是那么dp[i] = (i-1) * (dp[i-1] + dp[i-2])
这就是全错排公式,递推,递归都可以做
全错排递推AC代码:
#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]); } }