【MIPT Workshop Open 1 K】Blocks 题解
题目大意
有 $n$ 个柱子,高度构成 $1$~$n$ 的排列。现在你要把他们排在一行,使得从左边看能看到恰好 $l$ 根柱子,从右边看能看到恰好 $p$ 根柱子。求方案数。共 $m$ 组数据。
$n \leq 50000,\ \ l,p \leq 100,\ \ m \leq 10^5$
题解
可以很自然地想到 dp,从大到小放柱子,那么放在边上的就可以被看到,放到中间的就看不到。
这里要注意的是不要把左边和右边分开考虑,就是说不要什么分别算左边和右边然后合并,或者 dp 式子里设左边和右边分别看到了多少柱子。我不会告诉你我就是这样被卡了 2h 多。这样子始终会有一个 $O(l*p)$ 的时间。
但是可以发现一个新的柱子放两边其实是本质相同的!
所以设 $f_{i,j}$ 表示从大到小放了 $i$ 个柱子(方便起见,去掉最高的那个),两边可见的共有 $j$ 个,的方案数。新加进来一个柱子要么放两边使 $j$ 加一,要么放中间。
最后询问的时候乘个 $C_{p+q-2}^{p-1}$ 就好啦。
这样就是 $O(np+m)$ 的了。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include<bits/stdc++.h> #define fo(i,a,b) for(int i=a;i<=b;i++) using namespace std;
typedef long long LL;
const int maxn=5e4+5, maxp=205; const LL mo=1e9+7;
int n,p,q; LL f[maxn][maxp],C[maxp][maxp];
int m; int main() { n=50000, p=200; f[0][0]=1; fo(i,1,n-1) fo(j,1,min(p,i)) f[i][j]=(f[i-1][j-1]+f[i-1][j]*(i-1))%mo; fo(i,0,p) { C[i][0]=1; fo(j,1,i) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mo; } scanf("%d",&m); while (m--) { scanf("%d %d %d",&n,&p,&q); LL ans=f[n-1][p+q-2]*C[p+q-2][p-1]%mo; printf("%lld\n",ans); } }
|