上海理工大学第二届“联想杯”全国程序设计邀请赛 B-heith i ngra le 解题报告(组合数学)

题目大意:

在一个mxn网格上,填充格子为黑色构成山形,定义每一列最高的黑色格子的高度为该列的高度h,需要满足以下要求:
有一对l,r,满足1<=l<=r<=n
在1<=i<=l范围内, h i h_i hi不下降
l<=i<=r范围内, h i h_i hi保持不变
在r<=i<=n内, h i h_i hi不上升
求填充格子的方案数。

思路

好似可以dp,但我理不清转移Orz。考虑从整体进行组合计算。
考虑枚举l,r。若我们只是把l和r当作题目中的l和r,那么,不下降和不上升的不变部分可能会导致重复,所以,我们需要考虑枚举的l和r为平稳部分的边界。
则,枚举的上升部分列数为l-1,下降部分列数为n-r。接下来还是得考虑枚举平稳部分的高度h,则,上升部分和下降部分分别上升和下降h,考虑划分h。
我们考虑一个经典问题:方程 x 1 + x 2 + x 3 + … … + x n = h x_1+x_2+x_3+……+x_n=h x1+x2+x3++xn=h的解的个数,但是,因为l和r是边界条件,所以第一个元 x 1 x_1 x1需要大于0保证l和r是边界。因为,相当于方程 ( x 1 + 1 ) + x 2 + x 3 + … … + x n = h (x_1+1)+x_2+x_3+……+x_n=h (x1+1)+x2+x3++xn=h这个方程的解的个数,也就是 x 1 + x 2 + x 3 + … … + x n = h − 1 x_1+x_2+x_3+……+x_n=h-1 x1+x2+x3++xn=h1的解的个数。答案为 C h + n − 1 n − 1 C_{h+n-1}^{n-1} Ch+n1n1,最后还要+1(全白的情况)
据此,我们可以知道答案为 ∑ h = 1 m ∑ l = 1 n ∑ r = l n C l + h − 2 k − 1 ∗ C h − r + k − 1 k − 1 \sum_{h=1}^{m}\sum_{l=1}^{n}\sum_{r=l}^nC_{l+h-2}^{k-1}*C_{h-r+k-1}^{k-1} h=1ml=1nr=lnCl+h2k1Chr+k1k1
枚举三个变量肯定不行,考虑变形:
∑ h = 1 m ∑ l = 1 n ∑ r = l n C l + h − 2 k − 1 ∗ C h − r + k − 1 k − 1 = ∑ h = 1 m ∑ l = 1 n C l + h − 2 k − 1 ∑ r = l n C h − r + k − 1 k − 1 \sum_{h=1}^{m}\sum_{l=1}^{n}\sum_{r=l}^nC_{l+h-2}^{k-1}*C_{h-r+k-1}^{k-1} \\ =\sum_{h=1}^{m}\sum_{l=1}^{n}C_{l+h-2}^{k-1}\sum_{r=l}^nC_{h-r+k-1}^{k-1}\\ h=1ml=1nr=lnCl+h2k1Chr+k1k1=h=1ml=1nCl+h2k1r=lnChr+k1k1
考虑将最后一项递推预处理掉就行了(虽然这个预处理我想了好一会

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(int i = a; i <= n; ++ i)
#define per(i, a, n) for(int i = n; i >= a; -- i)
//#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');
}

template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}

int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int lcm(int a,int b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){
	ll ans=1;
	while(n){
		if(n&1) ans=(ans*a)%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans%mod;
}
//==============================================================
const int maxn=4e3+10;
int n,m;
ll f[maxn],finv[maxn];
int sum[maxn];
void init(){
	f[0]=1;
	for(int i=1;i<maxn;++i)f[i]=f[i-1]*i%mod;
	finv[maxn-1]=ksm(f[maxn-1],mod-2);
	for(int i=maxn-2;i>=0;--i){
		finv[i]=finv[i+1]*(i+1)%mod;
	}
}

ll C(ll n,ll m){
	//if(n<m)return 0;
	return f[n]*finv[n-m]%mod*finv[m]%mod;
}

ll pre[maxn][maxn];

int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	//clock_t c1 = clock();
	//===========================================================
	read(n),read(m);
	init();
	for(int l=n;l>=1;--l){
		for(int j=m;j>=0;--j){
			pre[l][j]=(pre[l+1][j]+C(n-l+j-1,j-1))%mod;
		}
	}
	ll ans=0;
	for(int k=1;k<=m;++k){
		for(int l=1;l<=n;++l){
			ans+=C(k-1+l-1,k-1)*pre[l][k]%mod;
			ans%=mod;
		}
	}
	cout<<(ans+1)%mod<<endl;
	//===========================================================
	//std::cerr << "Time:" << clock() - c1 << "ms" << std::endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值