[BZOJ2616]SPOJ PERIODNI(笛卡尔树+树形dp)
题面
http://darkbzoj.tk/problem/2616
题解
前置知识
先建出笛卡尔树,并定义几个变量:
\(lb[i]\)表示第i列左侧第一个低于i的位置。
\(rb[i]\)表示第i列右侧第一个低于i的位置。
\(dh[i]=h[i]-max(h[lb[i]],h[rb[i]])\)
\(S_i\)描述一个图形:截取原图中的lb[i]+1到rb[i]-1列,并把最下方h[i]行去掉。
然后就可以描述转移函数:$f[u][x] \(表示\)S_i$中放入x个车的方案数。
由于lb[u]+1到rb[u]-1这些点正好构成了笛卡尔树中u的子树,所以转化为一个树形dp。并且,有u的子树大小sz[u]=rb[u]-lb[u]+1。
for(int i = 0;i <= sz[lc[u]];i++)
for(int j = 0;j <= sz[rc[u]];j++)
for(int d = 0;i + j + d <= sz[u];d++)
f[u][i+j+d] += f[lc[u]][i] * f[rc[u]][j] % mod * C(sz[u]-i-j,d) % mod * P(dh[u],d) % mod);
其中lc[u],rc[u]代表u的左右子节点,C、P代表组合数与排列数。
这是为什么呢?\(S_u\)其实就是\(S_{lc[u]}\)与\(S_{rc[u]}\)“中间隔一格”拼在一起,再在下面加上\(dh[u]\)行所得。代码中d枚举的就是下面这dh[u]行中共放了几个车。如果\(S_{lc[u]}\)中放了i个车,\(S_{rc[u]}\)中放了j个车,那么\(S_u\)中还没被占用的列数就是\(sz[u]-i-j\)。在这些列中选出无序的d列,再在最下面添加的dh[u]行中选出有序的d行放车,这就解释了上面这个转移方程。
时间复杂度方面,由于i和j都只枚举到对应的sz,所以总时间复杂度为\(O(n^3)\)。
- P.S.这一过程还可以用卷积优化。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rg register
#define In inline
const int N = 500;
const ll mod = 1e9 + 7;
const ll W = 1e6;
namespace ModCalc{
In void Inc(ll &x,ll y){
x += y;if(x >= mod)x -= mod;
}
In void Dec(ll &x,ll y){
x -= y;if(x < 0)x += mod;
}
In ll Add(ll x,ll y){
Inc(x,y);return x;
}
In ll Sub(ll x,ll y){
Dec(x,y);return x;
}
}
using namespace ModCalc;
int n,k;
ll jc[W+5],iv[W+5];
ll power(ll a,ll n){
ll s = 1,x = a;
while(n){
if(n & 1)s = s * x % mod;
x = x * x % mod;
n >>= 1;
}
return s;
}
void prepro(){
jc[0] = 1;
for(rg int i = 1;i <= W;i++)jc[i] = jc[i-1] * i % mod;
iv[W] = power(jc[W],mod - 2);
for(rg int i = W - 1;i >= 0;i--)iv[i] = iv[i+1] * (i + 1) % mod;
}
In ll C(ll n,ll m){
if(n < m)return 0;
return jc[n] * iv[m] % mod * iv[n-m] % mod;
}
In ll P(ll n,ll m){
if(n < m)return 0;
return jc[n] * iv[n-m] % mod;
}
struct CartTree{
int top,rt;
int fa[N+5],lc[N+5],rc[N+5],sz[N+5];
ll h[N+5],dh[N+5],f[N+5][N+5];
int st[N+5];
In void build(){
for(rg int i = 1;i <= n;i++){
scanf("%lld",&h[i]);
while(top && h[st[top]] > h[i])
lc[i] = st[top--];
fa[i] = st[top];
if(!fa[i])rt = i;else rc[fa[i]] = i;
if(lc[i])fa[lc[i]] = i;
st[++top] = i;
}
f[0][0] = 1;
}
In void prepro(int u){
int l = u;while(lc[l])l = lc[l];
int r = u;while(rc[r])r = rc[r];
dh[u] = h[u] - max(h[l-1],h[r+1]);
}
In void dfs(int u){
if(lc[u])dfs(lc[u]);
if(rc[u])dfs(rc[u]);
sz[u] = sz[lc[u]] + sz[rc[u]] + 1;
for(rg int i = 0;i <= sz[lc[u]];i++)
for(rg int j = 0;j <= sz[rc[u]];j++)
for(rg int d = 0;i + j + d <= sz[u];d++)
Inc(f[u][i+j+d],f[lc[u]][i] * f[rc[u]][j] % mod * C(sz[u]-i-j,d) % mod * P(dh[u],d) % mod);
}
}T;
int main(){
scanf("%d%d",&n,&k);
prepro();
T.build();
for(rg int i = 1;i <= n;i++)T.prepro(i);
T.dfs(T.rt);
cout << T.f[T.rt][k] << endl;
return 0;
}