1. 程式人生 > >【MIPT Workshop Open 1 K】Blocks 題解

【MIPT Workshop Open 1 K】Blocks 題解

題目大意

       ~~~~~~ n n 個柱子,高度構成 1

1 ~ n n 的排列。現在你要把他們排在一行,使得從左邊看能看到恰好 l l 根柱子,從右邊看能看到恰好 p
p
根柱子。求方案數。共 m m 組資料。
       n <
= 50000 ,   l , p < = 100 ,   m < = 1 e 5 ~~~~~~n<=50000,~l,p<=100,~m<=1e5

題解

       ~~~~~~ 可以很自然地想到 dp,從大到小放柱子,那麼放在邊上的就可以被看到,放到中間的就看不到。

       ~~~~~~ 這裡要注意的是不要把左邊和右邊分開考慮,就是說不要什麼分別算左邊和右邊然後合併,或者 dp 式子裡設左邊和右邊分別看到了多少柱子。我不會告訴你我就是這樣被卡了 2h 多。這樣子始終會有一個 O ( l p ) O(l*p) 的時間。

       ~~~~~~ 但是可以發現一個新的柱子放兩邊其實是本質相同的!
       ~~~~~~ 所以設 f[i][j] 表示從大到小放了 i i 個柱子(方便起見,去掉最高的那個),兩邊可見的共有 j j 個,的方案數。新加進來一個柱子要麼放兩邊使 j j 加一,要麼放中間。
       ~~~~~~ 最後詢問的時候乘個 C p + q 2 p 1 C_{p+q-2}^{p-1} 就好啦。

       ~~~~~~ 這樣就是 O ( n p + m ) O(np+m) 的了。

程式碼

#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);
	}
}