递推 2016.6.19
来源:互联网 发布:淘宝悬浮导航分类代码 编辑:程序博客网 时间:2024/05/21 11:30
一、错排公式
1、
考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排
错排问题最早被尼古拉·伯努利和欧拉研究,因此历史上也称为伯努利-欧拉的装错信封的问题
这个问题有许多具体的版本,如在写信时将n封信装到n个不同的信封里,有多少种全部装错信封的情况?
又比如四人各写一张贺年卡互相赠送,有多少种赠送方法?自己写的贺年卡不能送给自己,所以也是典型的错排问题
2、递推的推导错排公式
当n个编号元素放在n个编号位置,元素编号与位置编号各不对应的方法数用D(n)表示
那么D(n-1)就表示n-1个编号元素放在n-1个编号位置,各不对应的方法数,其它类推
第一步,把第n个元素放在一个位置,比如位置k,一共有n-1种方法
第二步,放编号为k的元素,这时有两种情况:
⑴ 把它放到位置 n,那么,对于剩下的 n-1 个元素,由于第 k 个元素放到了位置 n,剩下 n-2 个元素就有 D(n - 2) 种方法
⑵ 第 k 个元素不把它放到位置 n,这时,对于这 n-1 个元素,有 D(n-1) 种方法
综上得到
D(n) = (n-1) * [D(n - 2) + D(n - 1)]
特殊地,D(1) = 0, D(2) = 1
HDU 2048 神、上帝以及老天爷
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const int mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 20 + 10;ull num[maxn];ull Factorial[maxn];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H Factorial[1] = 1; for (int i = 2; i <= 20; ++i) { Factorial[i] = Factorial[i-1] * i; } num[1] = 0; num[2] = 1; for (int i = 3; i <= 20; ++i) { num[i] = (i-1) * (num[i-1] + num[i-2]); } int C; scanf("%d", &C); while (C--) { int n; scanf("%d", &n); double ans = num[n] * 100.0 / (Factorial[n] * 1.0); printf("%.2f%%\n", ans); } return 0;}
HDU 2049 不容易系列之(4)——考新郎
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const int mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 20 + 10;ull num[maxn];ull Factorial[maxn];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H Factorial[1] = 1; for (int i = 2; i <= 20; ++i) { Factorial[i] = Factorial[i-1] * i; } num[1] = 0; num[2] = 1; for (int i = 3; i <= 20; ++i) { num[i] = (i-1) * (num[i-1] + num[i-2]); } int C; scanf("%d", &C); while (C--) { int n, m; scanf("%d%d", &n, &m); if (n == m) { printf("%I64d\n", num[n]); } else { ull ans = Factorial[n] / Factorial[m] / Factorial[n-m] * num[m]; printf("%I64d\n", ans); } } return 0;}
HDU 1465 不容易系列之一
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 20 + 10;ull num[maxn];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H num[2] = 1; num[3] = 2; for (int i = 4; i <=20; ++i) { num[i] = (i-1) * (num[i-1] + num[i-2]); } int n; while (scanf("%d", &n) != EOF) { printf("%I64d\n", num[n]); } return 0;}
二、分割问题
参考:http://blog.csdn.net/wu_lai_314/article/details/8219236
1、直线分割平面的最大数目
n 条直线最多可以把平面分割为多少个区域
假设当有 n-1 条直线时,平面最多被分成了 num[n-1] 个区域
则第 n 条直线要切成的区域数最多,就必须与每条直线相交且不能有同一交点
这样就会得到 n-1 个交点,这些交点将第 n 条直线分为 2 条射线和 n-2 条线段,而每条射线和线段将已有的区域一分为二
这样就多出了 2 +(n-2)= n 个区域
故 num[n] = num[n-1] + n
= num[n-2] + (n-1) + n
......
= num[1] + 2 + ... + n
= 2 + n * (n+1) / 2 - 1
= n * (n+1) / 2 + 1
2、折线分割平面
根据直线分平面可知,由交点决定了射线和线段的条数,进而决定了新增的区域数
当 n-1 条折线时,区域数为 num[n-1] 为了使增加的区域最多,则折线的两边的线段要和 n-1 条折线的边,即 2 *(n-1) 条线段相交
那么新增的线段数为 4 *(n-1),射线数为2
但要注意的是,折线本身相邻的两线段只能增加一个区域
故num[n] = num[n-1] + 4* (n-1) + 2 - 1
= num[n-1] + 4 * (n-1) + 1
= num[n-2] + 4 * (n-2) + 4 * (n-1) + 2
……
= num[1] + 4 + 4 * 2 + …… + 4 * (n-1) + (n-1)
= 2 * n^2 - n + 1
HDU 2550 折线分割平面
解题思路:
转自《 HDU 2000-2099 解题报告》
我们先来看一下 n 条相交的直线最多能把平面分割成几块很明显,当添加第 n 条直线时,为了使平面最多,则第 n 条直线要与前面 n-1 条直线都相交,切没有任何三条线交于一个点
这样,第 n 条直线一共有 n-1 个交点,我们知道,增加 n 个交点,则增加 n+1 个平面
所以 n 条直线分割平面最大数是1 + 1 + 2 + 3 + ... + n = (n^2 + n + 2) / 2
熟悉了线分割平面,现在,我们再来看看,每次增加的不是一条直线,而是两条相互平行的线,那又如何呢?
当第 n 次添加时,前面已经有 2n-2 条直线了,按我们上面讨论的知道,第 n 次添加时,第 2n-1 条直线和第 2n 条直线各能增加 2(n-1)+1 个平面
所以第 n 次添加增加的面数是 2[2(n-1) + 1] = 4n - 2 个。因此,总面数应该是1 + 4n(n+1)/2 - 2n = 2n^2 + 1
现在我们再来看如果把每次加进来的平行边让它们一头相交,情况又如何呢?
我们看到,平面 1、3 已经合为一个面,即少了一个面。因此,每当一组平行线相交后,就会减少一个面
因此,本题所要求的折线分割平面,自然就是上面求的的平行线分割平面数减去 n
即 2n^2 - n + 1
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H int C; scanf("%d", &C); int n; while (C--) { scanf("%d", &n); printf("%d\n", 2*n*n - n + 1); } return 0;}
3、封闭曲线分割平面
设有 n 条封闭曲线画在平面上,而任何两条封闭曲线恰好相交于两点,且任何三条封闭曲线不相交于同一点
问这些封闭曲线把平面分割成的区域个数
f(1) = 2
f(n) = f(n-1) + 2 * (n-1)
4、平面分割空间
由二维的分割问题可知,平面分割与线之间的交点有关,即交点决定射线和线段的条数,从而决定新增的区域数
试想在三维中则是否与平面的交线有关呢?
当有 n-1 个平面时,分割的空间数为 f(n-1)
要有最多的空间数,则第 n 个平面需与前 n-1 个平面相交,且不能有共同的交线,即最多有 n-1 条交线
而这 n-1 条交线把第 n 个平面最多分割成 g(n-1) 个区域,(g(n)为 n 条直线分割平面的最多个数)此平面将原有的空间一分为二,则最多增加 g(n-1) 个空间
g(n)=n(n+1)/2+1
1 + 2^2 + 3^2 + 4^2 + …… + n^2 = 1 / 6 * n * (n+1) * (2*n + 1) = 1 / 6 * (2 * n^3 +3 * n^2 + n)
故:
f(n) = f(n-1) + g(n-1)
= f(n-2) + g(n-2) + g(n-1)
……
= f(1) + g(1) + g(2) + …… + g(n-1)
= 2 + (1*2 + 2*3 + 3*4 + …… + (n-1)*n) / 2 + (n-1)
= (1 + 2^2 + 3^2 + 4^2 + …… + n^2 - 1 - 2 - 3 - …… - n ) / 2 + n + 1
= (n^3 + 5n) / 6 + 1
HDU 1290 献给杭电五十周年校庆的礼物
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 1000 + 10;int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H ull n; while (scanf("%I64d", &n) != EOF) { printf("%I64d\n", (n*n*n + 5*n) / 6 + 1); } return 0;}
HDU 1297 Children’s Queue
题意:
n 个人排队,女生不能单独排,问有多少种合法排列
例:4 个人有 7 种合法排列 FFFF, FFFM, MFFF, FFMM, MFFM, MMFF, MMMM (F 代表女生,M 代表男生)
解题思路:
用 F(n) 表示 n 个人的合法队列
做如下分析;
按照最后一个人的性别分析,他/她要么是男的,要么是女的,所以可以分为两大类讨论:
1、如果 n 个人的合法队列的最后一个人是男,则对前面 n-1 个人的队列没有任何限制,他只要站在最后即可
所以,这种情况一共有 F(n-1)
2、如果 n 个人的合法队列的最后一个人是女生,则要求队列的第 n-1 个人务必也是女生
这就是说,限定了最后两个人必须都是女生,这又可以分为两种情况:
2.1、如果队列的前 n-2 个人是合法的队列,则显然后面再加两个女生,也一定是合法的,这种情况有 F(n-2)
2.2、但是难点在于,即使前面的 n-2 个人不是合法的队列,加上两个女生也有可能是合法的
当然,这种 n-2 的不合法队列,不合适的地方必须是尾巴
就是说,这里说的长度是 n-2 的不合法串的形式必须是 “F(n-4) + 男 + 女”,这种情况一共有 F(n-4)
所以,通过以上的分析,可以得到递推的通项公式:
F(n) = F(n-1) + F(n-2) + F(n-4) (n > 3)
然后就是对 n <= 3 的一些特殊情况的处理了
显然:
F(0) = 1 (没有人也是合法的,这个可以特殊处理,就像 0 的阶乘定义为 1 一样)
F(1) = 1
F(2) = 2
F(3) = 4
n 可能到1000,3^100 ≈ 10^477 所以要用到高精度加法
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 1000 + 10;int num[maxn][510];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H memset(num, 0, sizeof(num)); num[0][0] = 1; num[1][0] = 1; num[2][0] = 2; num[3][0] = 4; for (int i = 4; i <= 1000; ++i) { for (int j = 0; j < 500; ++j) { num[i][j] += num[i-1][j] + num[i-2][j] + num[i-4][j]; } for (int j = 0; j < 500; ++j) { num[i][j+1] += num[i][j] / 10; num[i][j] %= 10; } } int n; while (scanf("%d", &n) != EOF) { int pos = 500; while (num[n][pos] == 0) { --pos; } while (pos >= 0) { printf("%d", num[n][pos]); --pos; } printf("\n"); } return 0;}
CodeForces_676B Pyramid of Glasses
题意:
杯子如题中图所示呈金字塔型摆放,从最顶层的正上方往下倒酒,倒满一个杯子需要的时间为 1 s
现在有 n 层杯子,问倒酒 t s 后有多少个杯子装满
解题思路:
每一层的第 n 个杯子的酒是由它的上一层第 n-1 和 第n 这两个酒杯溢出的酒量决定的
#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>using namespace std;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const int mod = 1e9 + 7;const int INF = 0x7fffffff;int glasses[20][70];int main(){#ifdef __AiR_H freopen("in.txt", "r", stdin);#endif // __AiR_H int n, t; scanf("%d%d", &n, &t); int Count = 0; memset(glasses, 0, sizeof(glasses)); int key = (1 << (n - 1)); t *= key; if (t >= key) { glasses[1][1] = t - key; ++Count; } for (int i = 2; i <= n; ++i) { for (int j = 1; j <= i; ++j) { glasses[i][j] = (glasses[i-1][j-1] + glasses[i-1][j]) / 2; } for (int j = 1; j <= i; ++j) { if (glasses[i][j] >= key) { glasses[i][j] -= key; ++Count; } else { glasses[i][j] = 0; } } } printf("%d\n", Count); return 0;}