牛客网暑期ACM多校训练营(第一场) B - Symmetric Matrix 公式推导 数形结合+动态规划

博客目录

链接:https://www.nowcoder.com/acm/contest/139/B
来源:牛客网
 

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld

题目描述

Count the number of n x n matrices A satisfying the following condition modulo m.
* Ai, j ∈ {0, 1, 2} for all 1 ≤ i, j ≤ n.
* Ai, j = Aj, i for all 1 ≤ i, j ≤ n.
* Ai, 1 + Ai, 2 + ... + Ai, n = 2 for all 1 ≤ i ≤ n.
* A1, 1 = A2, 2 = ... = An, n = 0.

输入描述:

The input consists of several test cases and is terminated by end-of-file.
Each test case contains two integers n and m.

输出描述:

For each test case, print an integer which denotes the result.

示例1

输入

复制

3 1000000000
100000 1000000000

输出

复制

1
507109376

备注:

* 1 ≤ n ≤ 105
* 1 ≤ m ≤ 109
* The sum of n does not exceed 107.

题目大意

给出一个n,向n * n的矩阵中填数字{0,1,2},要求矩阵是对称阵且每行元素之和恰好为2,且主对角线元素为0,求满足要求的矩阵个数。

分析过程

对称方阵可以看做某个图的邻接矩阵。其中aij=0表示点i和j不直接相连,=1表示i和j之间有一条直接相连的无向边,我们在这里称单边,=2表示i和j之间有两条直接相连的无向边,我们在这里称双边。当然,因为有2的存在导致这个图不是简单图。(可是我不会做图论题

将矩阵看成图之后,每行之和为2,由对称阵得每列之和也为2,图是无向图且每个点的度恰好为2。那么很容易想到以下结论:

1.如果一个点有一个双边(也就是长度为2的环),那么双边一定连着另一个点,这两个点组成一个连通分量。也就是不与第三个点相连

2.任意一个环,图论中很容易知道环中每个点度大于等于2,而本题要求度恰好为2,所以环一定没有分叉且也是一个连通分量,也就是环是纯粹一个环且不跟其他点相连,且删掉任意一个边之后环即退化成一条链。

以上两个结论我们可以知道,这个图可以分成若干连通分量,连通分量可以分为两类:1.双边(长度为2的环)  2.长度大于2的环

虽然看上去双边也是环,但是这个环比较特殊算起来有些不同,下面会提到。

动态规划

设f(n)表示有n个点的满足上述条件的图的个数。考虑第n个点,则有

枚举n点所属的连通分量环的长度k,则有2<=k<=n

设A(m,n)=m!/(m-n)!为排列数,表示从m个点中拿出n个并排列。

  • 环中必须有点n,故把n拿出来还剩下k-1个点,这些点可以任意排列,但是正反两种属于同一种环(所以总数除以2),所以组成一个环的方法数为:A(n-1,k-1)/2=(n-1)!/(n-k)!/2 ,剩余的点不与上述 环/双边 相连,故是剩余的点的情况是独立的,可以随意组合只要满足条件即可,则方法数为f(n-k),两者是独立的,所以方法数相乘:(n-1)!/(n-k)!*f(n-k)/2 ,3<=k<=n
  • 但是对于双边来讲,除了点x就只有一个点,直接A(n-1,1)=n-1就可以了,不需要除以2,这里体现了双边跟环的区别。而剩余的点有n-2个,也是独立的,所以要乘以f(n-2)。这个情况总数为:(n-1)f(n-2)

上述两个是并列的分类,所以相加:

f(n)=(n-1)f(n-2)+ ∑(3<=k<=n) {  (n-1)!/(n-k)!*f(n-k)/2        }

当然,这跟出题者给出的答案有些出入,原因是k的含义不同,我们只需要对k做一个简单处理就可以得到标准答案:

将k=n-k代入上式,得:

f(n)=(n-1)*f(n-2) + ∑(3<=n-k<=n) {  (n-1)!/(k)!*f(k)/2        }

     =(n-1)*f(n-2) + ∑(0<=k<=n-3) {  (n-1)!/(k)!*f(k)/2        }

显然:f(0)=f(1)=0 (f(0)的解释看代码注释),且k<=n-2等价于k<n-3所以

f(n)=(n-1)*f(n-2) + ∑(2<=k<n-2) {  (n-1)!/k!*f(k)/2        } ,n>=3

复杂度优化

上述式子是二次的复杂度,考虑化为递推式

我们令g(n)= ∑(2<=k<=n-3) {  (n-1)! / k!*f(k)/2        },n>=5

我们发现,式子中跟n有关的有两部分:

1.求和符号上限,我们可以提出最后一项把n变为n-1

2.大括号里面阶乘有n,我们可以将阶乘最后一项n-1提出来,将n变为n-1

所以可以改写成递推形式:

g(n)=把k=n-3的一项提出来 +  ∑ {  (n-1)*(n-1-1)! / k!*f(k)/2        }

g(n)=(n-1)!/(n-3)!*f(n-3)/2 +  (n-1)*∑(2<=k<=(n-1)-3) {  ((n-1)-1)! / k!*f(k)/2        }

      =(n-1)!/(n-3)!*f(n-3)/2 +  (n-1)*g(n-1)

AC代码

#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
#define regi ll
ll mod;
int const maxn=1e5+10;
ll f[maxn],g[maxn]; 
int main(){
    int n;
    f[0]=0;
    /*网上和其他人提交的代码令f(0)=1,但是讲道理应
    该是0,因为没有行就没办法让行求和等于2,
    博客中的推导也间接证明了这一点*/
    f[1]=0;
    f[2]=f[3]=1;
    g[0]=g[1]=g[2]=0 ;
    g[3]=1;
    while(cin>>n>>mod){
        for(regi i=4;i<=n;i++){
            f[i]=(i-1)*f[i-2]%mod;
            g[i]=(i-1)*(i-2)/2%mod*f[i-3]%mod;  //注意这里除以2不需要求逆元
            g[i]+=(i-1)*g[i-1]%mod;
            g[i]%=mod;
            f[i]+=g[i];
            f[i]%=mod;
        }
        printf("%lld\n",f[n]);
    }
}
/*
f(n)=(n-1)*f(n-2) + g(n)
g(n)=(n-1)(n-2)*f(n-3)/2 +  (n-1)*g(n-1)
*/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值