[BZOJ2616]SPOJ PERIODNI 树形dp+组合数+逆元

2616: SPOJ PERIODNI

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 128  Solved: 48
[Submit][Status][Discuss]

Description

Input

第1行包括两个正整数N,K,表示了棋盘的列数和放的车数。 
第2行包含N个正整数,表示了棋盘每列的高度。

Output

包括一个非负整数,表示有多少种放置的方案,输出答案mod 
1000000007后的结果即可。 
 

Sample Input

5 2
2 3 1 2 4

Sample Output

43

HINT

 

对于100%的数据,有 N≤500,K≤500,h[i] ≤1000000。

 

Source

 

我们可以先构造笛卡尔树(每次找最低点分为两半)

之后设f[i][j]表示以i为根的子树共放了j个的方案数。

每次dp时我们处理g[i]表示左右子树共放了i个的方案数。

长为n,宽为m的矩阵放k个车的方案数为C(n,k)*A(m,k)

依据这个每次枚举g[i]转移f即可。

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<algorithm>
 7 #define LL long long
 8 #define mod 1000000007
 9 using namespace std;
10 LL n,k;
11 LL g[1005],a[505];
12 LL f[505][505];
13 LL p[1000005],inv[1000005];
14 LL cal(LL x,LL y,LL k){
15   if(x<k||y<k)return 0;
16   return p[x]*inv[x-k]%mod*inv[k]%mod*p[y]%mod*inv[y-k]%mod;
17 }
18 LL dp(int l,int r,int h) {
19     if(l>r) return 0;
20     int lowi=l;
21     for(int i=l;i<=r;i++) if(a[lowi]>a[i]) lowi=i;
22     int la=dp(l,lowi-1,a[lowi]),ra=dp(lowi+1,r,a[lowi]);
23     memset(g,0,sizeof(g));
24     for(int i=0;i<=lowi-l;i++)
25         for(int j=0;j<=r-lowi;j++) g[i+j]=(g[i+j]+f[la][i]*f[ra][j])%mod;
26     for(int i=0;i<=r-l+1;i++)
27         for(int j=0;j<=i;j++) 
28             f[lowi][i]=(f[lowi][i]+g[j]*cal(r-l+1-j,a[lowi]-h,i-j))%mod;
29     return lowi;
30 }
31 LL power(LL x,LL ad) {
32     LL ans=1;
33     while(ad) {
34         if(ad&1) ans=ans*x%mod;
35         x=x*x%mod;
36         ad>>=1;
37     }
38     return ans;
39 }
40 int main() {
41     p[0]=1;
42     for(int i=1;i<=1000000;i++) p[i]=p[i-1]*i%mod;
43     inv[1000000]=power(p[1000000],mod-2);
44     for(int i=1000000-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
45     scanf("%lld%lld",&n,&k);
46     for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
47     f[0][0]=1;
48     printf("%lld\n",f[dp(1,n,0)][k]);
49 }
View Code

 

posted @ 2017-12-20 16:48  wls001  阅读(229)  评论(0编辑  收藏  举报