递推 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;}


0 0
原创粉丝点击