错排问题

1.错排问题很早就别研究起来,并且形式多样。最早的错排是伯努利-欧拉信封问题,n封信装入n个信封结果没有一封装入正确的地址的方案数。类似的还有写贺卡问题,n个人互相写贺卡互相赠送自己写的不能赠送给自己,问赠送的方案数。还有拿出来n本书放回去的时候恰好都不在原来的位置的方案数。等等很多类似的问题,错排的准确的意义就是每个元素都不在自己原来的位置。如果只是有几个元素不在自己原来的位置,不能叫做错排,这叫做非完全错排,当然这也是很好解决的,只要在这几个人中间进行错排,又转化为错排的问题。

2.错排的公式:错排的公式有很多种,每一种意义下的都有不同的公式,但是殊途同归,结果都是一样的,只不过出发点不同。我们给出几种错排的公式,但是只证明其中一种,其他的有兴趣的可以自己证明。

普通意义下的错排:D(n)=(n-1)*(D(n-1+D(n-2)),其实这个公式的推导很简单的,我们来严格证明一下(口嗨)。我们考虑第一个元素a1,他的位置可以有n-1中选择,假设它现在是在第i个位置,那么对于第i个元素就会有两种选择。

(1)i在a1原来的位置。那么其实a1和ai独立于整个排列。也就是D(n-2)

(2)i不在原来的位置,那么也很简单,这个时候a1已经排列好了,那么就可以丢掉了,继续考虑ai占据的这个元素的位置,其实也就是D(n-1)转移过来。

综合1,2可以得到。D(n)=(n-1)(D(n-1)+D(n-2)).当然通过容斥也是可以推导出来的,以及给出O(1)的公式。

容斥原理推导的错排公式:D(n) = n! - n!/1! + n!/2! - n!/3! + … + (-1)^n*n!/n! = ∑(k=2~n) (-1)^k * n! / k!,
简化版的公式:D(n) = [n!/e+0.5] ,其中e是自然对数的底,[x]为x的整数部分。

3.小数据范围下的错排数:

D(0) = 1(所有的元素都放回原位、没有摆错的情况)

D(1) = 0(只剩下一个元素,无论如何也不可能摆错)

D(2) = 1(两者互换位置)

D(3) = 2(ABC变成BCA或CAB)

D(4) = 9

D(5) = 44

D(6) = 265

D(7) = 1854

D(8) = 14833

D(9) = 133496

4.几个裸题:

//HDU1465
#include "stdafx.h"
#include<iostream>
using namespace std;
#define ll long long
ll mis[30];
int n;
void init()
{
	mis[0] = 1;
	mis[1] = 0;//一个元素不会排错
	for (int i = 2; i <= 30; i++)
	{
		mis[i] = (i - 1)*(mis[i-1] + mis[i - 2]);
	}
}
#pragma warning(disable:4996)
int main()
{
	int n;
	init();
	while (~scanf("%d", &n))
	{
		printf("%lld\n", mis[n]);
	}
    return 0;
}
l范围内只在不取模的情况下只能将mis计算到23,也就是D(22)是正确的。大于这个范围就是爆掉ll
//HDU2068
#include<iostream>
using namespace std;
#define ll long long
#pragma warning(disable:4996)
ll C(int n, int m)                //组合数公式
{
    ll u, d, i;         //组合数公式中的 分子u和分母d
    if (m>n / 2) m = n - m;                     //防止溢出 
    for (u = d = i = 1; i <= m; i++)
    {
        u = u * (n - i + 1);
        d = d * i;
    }
    return u / d;
}
int main()
{
    int i, M, N;
    ll f[14] = { 1,0,1 }, sum;
    for (i = 3; i <= 13; i++)
        f[i] = (i - 1)*(f[i - 1] + f[i - 2]);
    while (scanf("%d", &N), N)
    {
        for (sum = i = 0; i <= N / 2; i++)
            sum += C(N, N - i)*f[i];
        printf("%lld\n", sum);

    }
    return 0;
}
//HDU2049
#include<iostream>
using namespace std;
#define ll long long
#pragma warning(disable:4996)
ll C(int n, int m)                
{
    ll u, d, i;        
    if (m>n / 2) m = n - m;                     
    for (u = d = i = 1; i <= m; i++)
    {
        u = u * (n - i + 1);
        d = d * i;
    }
    return u / d;
}
int main()
{
    int i;
    int m, N;
    ll f[20] = { 1,0,1 }, sum;
    for (i = 3; i <= 20; i++)
        f[i] = (i - 1)*(f[i - 1] + f[i - 2]);
    int t;
    cin >> t;
    while (t--)
    {
        while (cin >> N >> m)
        {
        /*    for (sum = i = 0; i <= m; i++)
                sum += C(N, N - i)*f[i];*/
            sum = C(N, m)*f[m];
            printf("%lld\n", sum);

        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值