放棋子

发布时间 2023-06-14 10:27:56作者: admadm

放棋子

  • 看到题目后显然先想到 \(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]);
}

码风丑陋,见谅!