题意整理

  • 给定的矩阵以及个点。
  • 现在要将这个点放在矩阵里,并且保证第一行,第一列,最后一行,最后一列都有点。
  • 求总共有多少种方案。

方法一(动态规划)

1.解题思路

  • 初始化一个组合数组表示在i个格子里取j个点的组合数,即
  • 根据组合递归公式,可得:
  • 计算出所有的组合数之后,首先取对应的所有组合数,然后排除第1列或最后一列没点的情况以及第1行或最后一行没点的情况,加上多排除的1行1列没点的情况,加上多排除的第1列和最后一列没点的情况,加上多排除的第1行和最后一行没点的情况,继续排除2行1列没点的情况,继续排除1行2列没点的情况,加上多排除的2行2列没点的情况

动图展示:
图片说明

2.代码实现

import java.util.*;

public class Solution {
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @param k int整型 
     * @return int整型
     */
    //取余常数
    int mod=1000000007;
    //组合数组
    int[][] f;
    int n,m,k;

    public int solve (int n, int m, int k) {
        this.n=n;
        this.m=m;
        this.k=k;
        this.f=new int[n*m+1][k+1];
        //初始化组合数组
        initMap();
        //取k个点的所有组合数
        long res=f[n*m][k]%mod;
        //排除第1列或最后一列没点的情况
        res=(res-2*f[n*(m-1)][k]%mod+mod)%mod;
        //排除第1行或最后一行没点的情况
        res=(res-2*f[(n-1)*m][k]%mod+mod)%mod;
        //加上多排除的1行1列没点的情况
        res=(res+4*(long)f[(n-1)*(m-1)][k]%mod)%mod;
        //加上多排除的第1列和最后一列没点的情况
        res=(res+f[n*(m-2)][k])%mod;
        //加上多排除的第1行和最后一行没点的情况
        res=(res+f[(n-2)*m][k])%mod;
        //排除2行1列没点的情况
        res=(res-2*f[(n-2)*(m-1)][k]%mod+mod)%mod;
        //排除1行2列没点的情况
        res=(res-2*f[(n-1)*(m-2)][k]%mod+mod)%mod;
        //加上多排除的2行2列没点的情况
        res=(res+f[(n-2)*(m-2)][k]%mod)%mod;

        return (int)res;
    }

    //根据递推公式C(n,m)=C(n-1,m-1)+C(n-1,m),计算每一个组合数
    private void initMap(){
        //从i个格子中选一个点的情况,相当于C(i,1)
        for(int i=1;i<=n*m;i++){
            f[i][1]=i;
        }

        //从i个格子中选j个点的情况
        for(int i=1;i<=n*m;i++){
            for(int j=2;j<=k;j++){
                f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod;
            }
        }
    }
}

3.复杂度分析

  • 时间复杂度:初始化组合数组时,循环次数为,所以时间复杂度是
  • 空间复杂度:需要额外大小为的存储空间,所以空间复杂度是

方法二(数学)

1.解题思路

  • 思路和方法一的大致相同,仍然是先计算所有的组合数,然后排除掉不可能的情况,由于排除的情况可能会重叠,需要把多排除的情况再加上。
  • 计算组合数时,为了防止溢出,使用大数运算,先求大数阶乘,然后根据阶乘计算对应的组合。

2.代码实现

import java.util.*;
import java.math.BigInteger;

public class Solution {
    /**
     * 
     * @param n int整型 
     * @param m int整型 
     * @param k int整型 
     * @return int整型
     */
    //定义要用到的一些常量
    final BigInteger ONE=new BigInteger("1");
    final BigInteger TWO=new BigInteger("2");
    final BigInteger FOUR=new BigInteger("4");
    final BigInteger MOD=new BigInteger("1000000007");

    public int solve (int n, int m, int k) {
        //初始化结果变量
        BigInteger res=C(n*m,k);
        //排除不满足约束的情况,并加上多排除的情况
        res=res.subtract(C(n*(m-1),k).multiply(TWO));
        res=res.subtract(C((n-1)*m,k).multiply(TWO));
        res=res.add(C((n-1)*(m-1),k).multiply(FOUR));
        res=res.add(C(n*(m-2),k));
        res=res.add(C((n-2)*m,k));
        res=res.subtract(C((n-2)*(m-1),k).multiply(TWO));
        res=res.subtract(C((n-1)*(m-2),k).multiply(TWO));
        res=res.add(C((n-2)*(m-2),k));
        //取余
        res=res.mod(MOD);
        return res.intValue();
    }

    //计算大数组合
    private BigInteger C(int n,int m){
        return factorial(n).divide(factorial(m).multiply(factorial(n-m)));
    }

    //计算大数阶乘
    private BigInteger factorial(int n){
        BigInteger res=ONE;
        for(int i=2;i<=n;i++){
            BigInteger I=new BigInteger(String.valueOf(i));
            res=res.multiply(I);
        }
        return res;
    }
}

3.复杂度分析

  • 时间复杂度:计算阶乘的时间复杂度为,计算对应的组合需要计算常数次阶乘,所以计算组合数的时间复杂度为,总共需要计算常数次组合数,所以最终的时间复杂度是
  • 空间复杂度:需要额外常数级别的存储空间,所以空间复杂度是