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的元素,这时有两种情况:
- 把它放到位置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\).
下面通过这个递推关系推导通项公式:
为方便起见,设\(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!]\).
二项式反演
利用二项式反演我们更为简便的推导出一个通项公式。
考虑令 表示
个数字任意放的方案数,
表示
个数字都不放在自己位置上的方案数,通过枚举不在自己位置上的数字的个数容易得到
由二项式反演得到
注意到 ,这样我们就得到了他的通项公式,通过将
和组合数展开就可以得到更为简便的通项公式了
#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\))