版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/83049665
题意
给出n行,每行有人数限制num[i],并且num[i]>=num[i+1],总人数暂且称为tot=∑num[i],把1~tot这些数字填入矩阵,使得矩阵满足每行单调递增,每列单调递增,求满足要求的矩阵数目
分析
一开始是打着做dp的名号,发现了这道题
在《算法竞赛》这本书上,是这样讲的
线性dp
但实际上,由于这道题空间限制比较严,我们dp的话会爆内存(上一篇博客有解释)
所以就凉了……
网上盛传的做法是利用杨氏矩阵和钩子公式,我就去学习了一下
本来以为几行代码就搞定,是一道水题,后来发现还是要思考一下的
先来讲一下杨氏矩阵吧
杨氏矩阵定义(需满足的条件/特征):
(1)若格子(i,j)没有元素,则该格子的右边和上边一定没有元素;
(2)若格子(i,j)有元素data[i][j],则该格子右边和上边相邻的格子要么没有元素,要么有比data[i][j]大的元素。
显然有同一些元素组成的杨氏矩阵不唯一,1~n组成杨氏矩阵的个数可以写出:
F[1]=1,F[2]=2, F[n]=F[n-1]+(n-1)*F[n-2] (n>2)。
(这个东西不会证……但我们很容易发现这简直就是这道题的模样啊)
再来说一下钩子公式(wcr大佬表示非常喜欢这个名字??)
对于给定形状,不同的杨氏矩阵的个数为(n!/(每个格子的钩子长度加1的积))。
钩子长度:该格子右边的格子数和它上边的格子数之和;
然后就会发现这道题简直就是为这个公式的应用而生的
只是……
如果我们简单套公式的话就会出岔子
因为结合这道题,从前往后值是递增的,下面的数是大于当前数的,所以我们换一下,把下面看做上面(相当于把矩阵180度旋转一下)
还有一点由于和阶乘有关,我们很容易就炸掉了,所以要一边处理一边约分
代码
#include<cstdio>
#include<iostream>
#include<cstring>
#define ll long long
using namespace std;
int k,num[10];
ll sum[200];
ll gcd(ll x,ll y){
ll z=x%y;
while(z){x=y;y=z;z=x%y;}
return y;
}
int main(){
while(1){
memset(sum,0,sizeof(sum));
scanf("%d",&k);
if(k==0) break;
int i,j,n=0;
for(i=1;i<=k;++i) scanf("%d",&num[i]);
for(i=1;i<=k;++i){
for(j=1;j<=num[i];++j){
++n;
for(int p=i+1;p<=k;++p){
if(num[p]>=j) sum[n]++;
else break;
}
sum[n]+=num[i]-j+1;
}
}
ll x=1,y=1;
for(i=1;i<=n;++i){
x*=i;y*=sum[i];
ll tmp=gcd(x,y);
x/=tmp;y/=tmp;
}
printf("%I64d\n",x/y);
}
return 0;
}