JZOJ5164【NOIP2017模拟6.25】小A做CF

题目

Description

目标rating超过CLJ的小A最近在疯狂做CF,话说这次CF在赛前十分钟出了个相当奇葩的预选题,要求十分钟内必须做出这道题才能参加这次CF。
这道题是这样的,给你一个N*N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。
最近被各种神题折磨得死去活来,走火入魔的小A一下被这个超级水题给卡住了,但是这次是超过CLJ的最佳机会,于是,心急火燎的他找到了你,请你带他顺利参加这次CF正赛吧。

Input

第一行一个N,接下来一个N*N的矩阵。

Output

合法方案数。

Sample Input
2
0 1
1 0
Sample Output
1
Data Constraint

20%的数据保证: N<=10
60%的数据保证: N<=20
100%的数据保证: N<=200

题解

20%爆搜+剪枝

60%爆搜+更好的剪枝来骗分(本验题人未打过这档分,所以拿不到的也没办法)

100%有两种方法,一种是原题解方法,一种是本验题人方法。

法一:错排公式 (粘贴自百度百科)

递推公式

\(n\)个编号元素放在\(n\)个编号位置,元素编号与位置编号各不对应的方法数用\(D(n)\)表示,那么\(D(n-1)\)就表示\(n-1\)个编号元素放在\(n-1\)个编号位置,各不对应的方法数,其它类推.

第一步,把第\(n\)个元素放在一个位置,比如位置\(k\),一共有\(n-1\)种方法;

第二步,放编号为k的元素,这时有两种情况:

  1. 把它放到位置n,那么,对于剩下的\(n-1\)个元素,由于第k个元素放到了位置\(n\),剩下\(n-2\)个元素就有\(D(n-2)\)种方法;
  2. 第k个元素不把它放到位置\(n\),这时,对于这\(n-1\)个元素,有\(D(n-1)\)种方法;

综上得到

\(D(n) = (n-1) [D(n-2) + D(n-1)]\)

特殊地,\(D(1) = 0,D(2) = 1\).

下面通过这个递推关系推导通项公式:

为方便起见,设\(D(k) = k! N(k), k = 1, 2, …, n\),

\(N(1) = 0, N(2) = 1/2\).

\(n ≥ 3\)时,\(n! N(n) = (n-1) (n-1)! N(n-1) + (n-1)! N(n-2)\)

\(nN(n) = (n-1) N(n-1) + N(n-2)\)

于是有

\(N(n) - N(n-1) = - [N(n-1) - N(n-2)] / n = (-1/n) [-1/(n-1)] [-1/(n-2)]…(-1/3) [N(2) - N(1)] = (-1)^n / n!\)

因此

\(N(n-1) - N(n-2) = (-1)^(n-1) / (n-1)!,\)$

\(N(2) - N(1) = (-1)^2 / 2!\).

相加,可得

\(N(n) = (-1)^2/2! + … + (-1)^(n-1) / (n-1)! + (-1)^n/n!\)

因此

\(D(n) = n! [(-1)^2/2! + … + (-1)^(n-1)/(n-1)! + (-1)^n/n!]\).

此即错排公式。

(简化:\(f[i]=(i-1)*(f[i-1]+f[i-2])\)

容斥原理

用容斥原理也可以推出错排公式:

正整数1, 2, 3, ……, n的全排列有 n! 种,其中第k位是k的排列有 (n-1)! 种;当k分别取1, 2, 3, ……, n时,共有n*(n-1)!种排列是至少放对了一个的,由于所求的是错排的种数,所以应当减去这些排列;但是此时把同时有两个数不错排的排列多排除了一次,应补上;在补上时,把同时有三个数不错排的排列多补上了一次,应排除;……;继续这一过程,得到错排的排列种数为

\(D(n) = n! - n!/1! + n!/2! - n!/3! + … + (-1)^n * n!/n! = ∑(k=2~n) (-1)^k * n! / k!\),

\(D(n) = n! [1/0! - 1/1! + 1/2! - 1/3! + 1/4! + ... + (-1)^n/n!]\).

二项式反演

利用二项式反演我们更为简便的推导出一个通项公式。

考虑令 img 表示 img 个数字任意放的方案数, img 表示 img 个数字都不放在自己位置上的方案数,通过枚举不在自己位置上的数字的个数容易得到

img

由二项式反演得到

img

注意到 img ,这样我们就得到了他的通项公式,通过将 img 和组合数展开就可以得到更为简便的通项公式了

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int n, w[210][500], p[500], q[500], len = 1, l, k1;
void jia(int k)
{
    memset(q, 0, sizeof q);
    for (int i = 1; i <= len; i++)
        q[i] = w[k - 2][i] + w[k - 1][i];
    for (int i = 1; i <= len; i++)
    {
        q[i + 1] += q[i] / 10;
        q[i] %= 10;
    }
    while (q[len + 1]) len++;
}
void cheng(int k)
{
    jia(k);
    l = 0, k1 = k;
    memset(p, 0, sizeof p);
    while (k1 >= 10)
    {
        p[++l] = k1 % 10;
        k1 /= 10;
    }
    if (k1) p[++l] = k1;
    for (int i = 1; i <= len; i++)
        for (int j = 1; j <= l; j++)
            w[k][i + j - 1] += q[i] * p[j];
    for (int i = 1; i < len + l; i++)
    {
        w[k][i + 1] += w[k][i] / 10;
        w[k][i] %= 10;
    }
    len = len + l - 1;
    while (w[k][len + 1]) len++;
}
int main()
{
    memset(w, 0, sizeof w);
    scanf("%d", &n);
    w[1][1] = 1;
    w[2][1] = 2;
    for (int i = 3; i < n; i++) cheng(i);
    for (int i = len; i >= 1; i--) printf("%d", w[n - 1][i]);
    printf("\n");
    return 0;
}

法二:验题人解法

基于转化后的模型,可以发现,对于空白棋盘方案数是相当容易求出来的,而带障碍棋盘方案可以通过容斥原理实现 (即将总的减去放一个棋子在障碍上再加回两个棋子在障碍上……)预处理出组合数(因为放障碍时要选位置),可以得出如下式子:(设有\(n\)\(n\)列,\(m\)个障碍) \(Ans=∑(-1) i*C i m*(n-i)!\)\(i\)枚举障碍数,由 0 到 \(m\)

转载于:https://www.cnblogs.com/featherZHY/p/11334062.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值