FJOI2016 建筑师
昨天考了场省选模拟赛,下来才知道是FJOI2016,不过T1,T2都没在OJ上找到??
T2倒是以前在紫书上面的UVa 1638做到过类似的.
题意概述
给
n
个建筑,高度为
数据范围:
10%:1≤n≤10
20%:1≤n≤100
40%:1≤n≤5∗104,1≤T≤5
100%:1≤n≤5∗104
,
1≤a,b≤100,1≤T≤2∗105
.
题目分析:
(为了叙述方便,将建筑视为杆子)
20% 的解法
20% 的话和UVa 1638是一样的.
定义状态
f(i,j,k)
表示由高度为
1
~
因为对于
则对于
1
~
由于考虑最高的杆子会挡住低的杆子,比较麻烦.
所以从
i−1
转移到
i
,可以考虑
若放在最左边,左边加
1
,方案数为
若放在最右边,右边加
1
,方案数为
而若
1
号放在中间的话,不会被看见.
综上所述,转移方程如下
时间复杂度为 O(Tnab) .
40% 的解法
其实可以发现在上面的方法中左右两边是一样的.
基于此,考虑降维,减少状态.
用
f(i,j)
表示用i个杆子,然后最大的放在最右边,从左能看到
j
个杆子的方案数.
和上面类似的,可以得到转移方程
那么最后的答案为
时间复杂度为 O(Tn∗max(l,r)) .
100% 的解法
话说这个题,我在涨老师的博客上看到了一种特别妙的解法,据说是汪神搞出来的.
先说一下答案(其中
su
表示无符号第一类斯特林数)
下面来解释一下
观察上图,这是从左和从右能看到杆子的一个汇总.
(因为
n
号杆子无论放在何处均可见,故在下面证明的时候不包含
以左边为例,一个可见杆子之后跟着一些不可见杆子,如
1
~
对于每一个集合而言,除了最高的杆子(即可见杆子)的位置必须放在最前面以外,其他的杆子其实是可以随意放置的.那么可将其连起来形成一个圆,对于一个圆而言,若从任意一处切开形成的序列中合法序列(即满足最高杆子在最前面)有且仅有一个,即一个圆仅代表一种方案.假设这个集合有
k
根杆子,那么对于该集合的合法方案就有
那么对于一共的
n−1
根杆子来说,就有
su(n−1,a+b−2)
种方案.
这只是保证了有
a+b−2
根杆子可见,而对于要使得左边看见
a
根杆子而言,即在这
综上所述,满足条件的方案数为 su(n−1,a+b−2)∗C(a+b−2,a−1) .
基于此,可以做到 O(na+a2) 预处理, O(1) 回答询问.
代码实现:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
int read() {
char ch=getchar();int ret=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret;
}
const int MOD=1e9+7;
const int maxa=200+10;
const int maxn=50000+10;
int s[maxn][maxa];//s(p,k)表示p个元素组成k个圆排列的方案数 即第一类斯特林数
int C[maxa][maxa];//C(n,m)表示n个元素中取出m个元素的方案数 即组合数
void init(int n,int a) {
for(int i=0;i<=n;i++) {
s[i][0]=0;
if(i<=a) s[i][i]=1;
for(int j=1;j<=min(i,a);j++) s[i][j]=(0ll+s[i-1][j-1]+1ll*(i-1)*s[i-1][j]%MOD)%MOD;
}
for(int i=0;i<=a;i++) {
C[i][0]=1;
for(int j=1;j<=i;j++) C[i][j]=(0ll+C[i-1][j]+C[i-1][j-1])%MOD;
}
}
int main() {
freopen("building.in","r",stdin);
freopen("building.out","w",stdout);
init(50000,200);
int T=read(),n,a,b;
while(T--) {
n=read(),a=read(),b=read();
printf("%lld\n",(1ll*s[n-1][a+b-2]*C[a+b-2][a-1])%MOD);
}
return 0;
}