Symmetric Matrix (数学想法题)

原题:牛客网暑期ACM多校训练营(第一场)B

题意:

给一个矩阵,所有格子里面都是0,1,2,主对角线(i==j)为0,要求每行每列的和都是2,求n*n时的方案数

解析:

出题人比较刁难了,这个矩阵其实应该看成一个图的邻接矩阵,要不然很难做

每行每列的和都是2的意思是,每个点和其他点之间的路径有且只有两条

M[i][j]==2:i和j之间有两条路径,且自成一环,不能和其他点之间有路径


我们用dp[i]来表示i个点的方案数

对于dp[i],有两部分可以转化过来

  1. 在前面i-1个点中选取任意一个和i自成一环,即:(i-1)*dp[i-2]
  2. 从dp[k]转化过来,也就是i和i-1-k个点形成一个环,即Σ(0<=k<=i-3) dp[k]*(i-1)*(i-2)...*(i-k-1)/2 (因为环的两个顺序都计算了,所以最后要除2)

那么就可以得到状态转移方程了

dp[i]=(i-1)* dp[i-2]+ Σ(0<=k<=i-3) dp[k](i-1)!/k!/2

这个时候就有问题了,n=1e5,每次对于后半部分都求一次很可能会T,所以我们需要保持下后半部分,最好是找到一个递推方程

设dp[i]的后半部分为F[i] (Σ(0<=k<=i-3) dp[k](i-1)!/k!/2)
那么F[i+1]就是 (Σ(0<=k<=i-2) dp[k](i)!/k!/2)
多出一项dp[i-2]/(i-2)! /2且(i-1)!变成(i)!

所以有递推方程F[i+1]=(F[i]/(i-1)!+dp[i-2]/(i-2)!/2)*i!

化简得:F[i+1]=F[i]*i+dp[i-2]*i*(i-1)/2
或者是:F[i]=F[i-1]*(i-1)+dp[i-3]*(i-1)*(i-2)/2

那么既然两部分都退出来了,就可以线性求解了

代码:

这道题我当时卡的快要吐血了,dp[0]=1,dp[1]=0,还有循环里面如果不加强制转换的话要把i定义成long long
前三个g[i]是可以退出来的



D read(){ D ans=0; char last=' ',ch=getchar();
while(ch<'0' || ch>'9')last=ch,ch=getchar();
while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();
if(last=='-')ans=-ans; return ans;
}

D mod,n;
D dp[100009],g[100009];

int main(){
    while(~scanf("%lld%lld",&n,&mod)){
        dp[0]=1;dp[1]=0;dp[2]=1;g[1]=g[2]=g[0]=0;
        for(D i=3;i<=n;i++){
            g[i]=(g[i-1]*(i-1)%mod+(i-1)*(i-2)/2%mod*dp[i-3])%mod;
            dp[i]=((i-1)*dp[i-2]%mod+g[i])%mod;
        }
        printf("%lld\n",dp[n]);
    }
}




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值