【Luogu P4609】建筑师

题目链接

题目描述

小 Z 是一个很有名的建筑师,有一天他接到了一个很奇怪的任务:在数轴上建 n 个建筑,每个建筑的高度是 1 到 n 之间的一个整数。

小 Z 有很严重的强迫症,他不喜欢有两个建筑的高度相同。另外小 Z 觉得如果从最左边(所有建筑都在右边)看能看到 A 个建筑,从最右边(所有建筑都在左边)看能看到 B 个建筑,这样的建筑群有着独特的美感。现在,小 Z 想知道满足上述所有条件的建筑方案有多少种?

如果建筑 i 的左(右)边没有任何建造比它高,则建筑 i 可以从左(右)边看到。两种方案不同,当且仅当存在某个建筑在两种方案下的高度不同。

Sol

题意:
求满足如下条件的排列个数:
从左往右前缀最大值有 A 种 , 从右往左后缀最大值有 B 种

思路:
先找几个显然的结论 , 首先最高的那个显然能够被左右同时看到,并且它把左右两边分割开来了,两边互不影响

这样左边只需要有 A − 1 A-1 A1 个被看到 , 右边则只要 B − 1 B-1 B1

显然的只考虑一边时能够被看到的楼的高度是递增的 , 而两座能够被看到的楼中间的楼只要小于它们两个就可以随便排列

先只考虑从左往右 , 那么由于第一座楼是一定能够被看到的 , 我们考虑把一座能被看到的楼和到下一座能被看到的楼之间的楼看作一组 , 这样由于最高的楼的分水岭作用 , 排列就一定是不重不漏的

现在考虑每一组 , 能被看到的都是最左边那个最高的那个 , 而如果有多个组 , 我们如果把每组按最高的高度(就是最左边能被看到的楼的高度) 从小往大进行排序的话 , 那么一定是恰好有组数个楼能被看到 , 这里要钦定每一组内的最高的在最左边(不然根据我们的定义他们就不会在一个组里)

然后右边也是类似的 , 所以问题变成了把 n-1 个元素分成如上的 A − 1 A-1 A1个组 和 B − 1 B-1 B1个组
如果每组的元素确定 , 那么最高的楼的位置也是确定的

其实左右是可以统一的 , 强制让最高的在他们所在的边就行了

对于一组确定元素的组来说 , 自然他们的排列方式就有 ( n − 1 ) ! (n-1)! (n1)!种(n是组内元素个数)
这是因为最高的放的位置是确定的

但是我们现在相当于要 把 n − 1 n-1 n1个元素划分成 A + B − 2 A+B-2 A+B2 个这样的东西

所以这就是第一类斯特林数了 , 相当于是 ( n − 1 ) (n-1) (n1)个人去坐 A + B − 2 A+B-2 A+B2 张圆桌

为什么是圆排列? 因为第一个是确定的 , 也就是从最高的位置顺时针数相同的为一种方案

所以我们就搞定了这个东西

最后只需要再乘上一个组合数就行了 , 因为要把这 A + B − 2 A+B-2 A+B2 个组分配到左右两边

答案就是:
C [ A + B − 2 ] [ A − 1 ] ∗ S [ n − 1 ] [ A + B − 2 ] C[A+B-2][A-1]*S[n-1][A+B-2] C[A+B2][A1]S[n1][A+B2]

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
inline void init(int &x){
	int t=1;char ch=getchar();x=0;
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
	for(;ch<='9'&&ch>='0';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
	return (void)(x*=t);
}
void Print(int x){
	if(!x) return void(putchar('0'));
	if(x>=10) Print(x/10);
	putchar(x%10+'0');
}
const int N=5e4+10;
int C[201][201],S[50001][201];
inline void Pre()
{
	C[0][0]=1;
	for(int i=1,j;i<=200;++i)
		for(j=1,C[i][0]=1;j<=i;++j)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	S[0][0]=1;
	for(int i=1;i<=50000;++i) {
		int ed=min(i,200);
		for(int j=1;j<=ed;++j) {
			S[i][j]=S[i-1][j-1]+1ll*S[i-1][j]*(i-1)%mod;
			if(S[i][j]>=mod) S[i][j]-=mod;
		}
	}
	return;
}
int main()
{
	int T;init(T);Pre();
	while(T--){
		int n,A,B;init(n);init(A);init(B);
		if((!A)||(!B)) {puts("0");continue;}
		printf("%lld\n",1ll*C[A+B-2][A-1]*S[n-1][A+B-2]%mod);
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值