FJOI 2016 建筑师(斯特林数)

本文介绍了一道编程竞赛题“建筑师”的解决方案,该题要求计算在特定条件下,不同高度建筑物布局的总数。文章详细解释了如何利用第一类斯特林数和组合数学方法来高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【FJOI2016】建筑师

问题描述

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

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

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

输入格式

第一行一个整数 T,代表 T 组数据。
接下来 T 行,每行三个整数 n,A,B

输出格式

对于每组数据输出一行答案 mod10^9+7。

样例输入 1

2
3 2 2
3 1 2

样例输出 1

2
1

样例输入 2

5
1 1 1
2 1 1
4 3 1
10 2 2
8 6 4

样例输出 2

1
0
3
219168
0

提示

对于 10% 的数据 : 1≤n≤10
对于 20% 的数据 : 1≤n≤100
对于 40% 的数据 : 1≤n≤50000, 1≤T≤5
对于 100%的数据 :1≤n≤50000, 1≤A,B≤100, 1≤T≤200000


直接dp可以拿到40分,令F[i][j]表示i个数,从一端能看到j个递推即可。

标算需要用到第一类斯特林数。

考虑最高的建筑,他一定能被看到,那么他左右两边各还有A1,B1个建筑能被看到。
左右是对称的,因此先讨论左边,那么显然每一个能被看到的建筑后面可能有一些建筑物被挡住了,考虑将每一个建筑物和他后面被挡住的建筑物,
假设一个建筑物挡住了d个建筑,那么他们可能的构成方案有d!种,即等价于d+1个数,给定了一个数放于队首,其他数随意排列的方案数,等价于d+1个数的圆排列数目。

那么总共应该有A1个这样的圆排列,同时考虑右边的情况,问题等价于将n1(不考虑最高的)个数,划分成A+B2个圆排列的方案数,就是第一类斯特林数,记为SA+B2n1,同时,这样的圆要选A1个放到左边,因此还要乘上CA1A+B2

因此最后的答案就是SA+B2n1×CA1A+B2

斯特林数和组合数都递推预处理,可以做到O(1)回答,O(nA+A2)预处理

第一类斯特林数递推式:Skn=(n1)Skn1+Sk1n1


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define ll long long
#define N 50005
using namespace std;
const ll mod=1e9+7;
ll T,n,A,B,C[205][105],S[N][205];
void pre()
{
    ll i,j,k;
    for(i=0;i<205;i++)C[i][0]=1;
    for(i=1;i<205;i++)
    for(j=1;j<=i&&j<105;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    for(i=0;i<205;i++)S[i][i]=1;
    for(i=2;i<N;i++)
    for(j=1;j<i&&j<205;j++)S[i][j]=((i-1)*S[i-1][j]%mod+S[i-1][j-1])%mod;
}
int main()
{
    scanf("%lld",&T);pre();
    while(T--)
    {
        scanf("%lld%lld%lld",&n,&A,&B);
        if(A+B>n+1){puts("0");continue;}
        printf("%lld\n",C[A+B-2][A-1]*S[n-1][A+B-2]%mod);
    }
}
### 关于洛谷平台上线段树练习题目的推荐 #### 题目一:P3810 【模板】线段树 1 此题目作为入门级的线段树构与基本操作实现,适合初学者掌握线段树的基础概念和简单应用。通过这道题可以熟悉如何在线段树上执行单点更新以及区间求和的操作[^2]。 ```cpp #include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 5; struct SegmentTree { int sum[maxn << 2]; void pushUp(int rt) {sum[rt] = sum[rt<<1] + sum[rt<<1|1];} void build(int l, int r, int rt){ if(l == r){ scanf("%d", &sum[rt]); return ; } int m = (l+r)>>1; build(lson); build(rson); pushUp(rt); } // Other functions like update and query can be implemented here. }; int main(){ SegmentTree tree; int n,m,opt,x,y,k; char str[2]; while(scanf("%d%d",&n,&m)!=EOF){ memset(tree.sum,0,sizeof(tree.sum)); tree.build(1,n,1); for(int i=0;i<m;++i){ getchar(); gets(str); sscanf(str,"%d %d %d",&opt,&x,&y); switch(opt){ case 1 : k=y-tree.a[x]; tree.update(x,k,1,n,1);break; case 2 : printf("%lld\n",tree.query(x,y,1,n,1)); break; } } } return 0; } ``` #### 题目二:P2791 幼儿园篮球题 该问题不仅考察了对线段树的理解程度,还涉及到更复杂的逻辑思考能力。在这个场景下,需要利用线段树来高效解决有关子树内节点权重总和的问题,在实际编程竞赛中具有较高的实用性价值[^1]。 #### 题目三:P4566 [FJOI2018]新型城市化 这是一个较为高级的应用实例,涉及动态开点线段树的知识点。对于想要深入研究据结构优化技巧的学习者来说是一个很好的挑战对象。这类题目有助于提高解决问题的能力,并加深对复杂算法设计模式的认识。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值