放棋子
放棋子
-
看到题目后显然先想到 \(DP\) ,但是 \(N\ge200\) ,那就肯定不行了。
-
题目理解:障碍与棋子一样,都是每行,列只有一个,因此,我们思考这样一个问题:
\[\begin{matrix}
1&0&0&0&&&&&&1&0&0&0\\
0&0&1&0&&&\implies&&&0&1&0&0\\
0&1&0&0&&&&&&0&0&1&0\\
0&0&0&1&&&&&&0&0&0&1
\end{matrix}
\]
这两种情况的方案数有何区别?
-
答案是并无区别:显然第一种矩阵经过行与行(或列于列)的变换就能转化为第二种,而每一行每一列都会有一个障碍,因此,每一种矩阵都能转化为第二种,转化后每一行(或列)的方案数不变,总方案数当然也不变。
-
因此我们不考虑输入的矩阵,直接转化为第一种,认为障碍均在对角线上,于是,原问题就成了:在一个对角线不可放的棋盘放棋子,且每一列,每一行均只有一枚棋子。
-
进一步转化,问题就成了:一个数列 \(a_n(a_i\not=i)\) 求方案数,这就是一个裸的错排问题了,例题:信封问题,没做过的同学可以自行了解一下,这里直接给出递推式:
\[\begin{aligned}
f_1&=0\\f_2&=1\\f_n&=(n-1)\times (f_{n-1}+f_{n-2})
\end{aligned}
\]
-
考虑到 \(f_n\) 的增长速率极快,需要高精度辅助实现。
#include<bits/stdc++.h>
#define ll long long
const ll p=10000000000000000;
const int len=100010;
ll f[201][len],tem,pos;
void work(int n){
for(int i=1;i<=len;++i){
tem=f[n-1][i]+f[n-2][i];
f[n][i]=(tem+pos)%p;
pos=tem/p;
}
for(int i=1;i<=len;++i){
tem=f[n][i]*(n-1);
f[n][i]=(tem%p+pos);
pos=tem/p;
}
}
int main(){
int n;scanf("%d",&n);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
int a;scanf("%d",&a);
}没什么用的输入
if(n==1) return puts("0"),0;
if(n==2) return puts("1"),0;
f[1][1]=0;f[2][1]=1;
for(int i=3;i<=n;++i) work(i);
int len1(100010);
while(f[n][len1]==0&&len1>1) len1--;
printf("%lld",f[n][len1]);
while(--len1) printf("%016lld",f[n][len1]);
}
码风丑陋,见谅!