1. 程式人生 > 實用技巧 >SP3734 PERIODNI - Periodni(笛卡爾樹)

SP3734 PERIODNI - Periodni(笛卡爾樹)

據說是一道笛卡爾樹經典例題,本蒟蒻拿來加強一下理解的(只簡單地總結下為什麼用笛卡爾樹,以及笛卡爾樹的性質,之後樹形DP的細節就不講了),所以想看題解的或許可以轉到別的大佬那邊去了

連結:https://www.luogu.com.cn/problem/SP3734

其實樣例已經說的已經很清楚了,兩個數會不會相互影響,取決於中間有沒有一個高度比它們都小的矩形把它們隔開,如果我們把矩形高度視為數列,而一個區間最小的矩形高度就是瓶頸值,即是關鍵的,受這個瓶頸值控制,於是對於這種關鍵取決於最小值之類的題,我們常常用堆結構來限制,如kruskal重構樹,又如本題的笛卡爾樹.所以我們以數列編號為BST關鍵字,矩陣高度為堆關鍵字,即可把一個不規則圖形拆成若干個規則的矩形,

從而解決這道題

/*SP3734 PERIODNI - Periodni*/
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn = 1e3 + 10;
int lc[maxn],rc[maxn];
int dp[maxn][maxn];
int f[maxn];
int h[maxn];
int st[maxn],top;
int siz[maxn];
bool nrt[maxn];
const int mod = 1e9 + 7;
const int maxsize = 1e6 + 10;
int fa[maxn];
int jc[maxsize],invjc[maxsize];
int n,cnt;
int C(int x,int y){
	return 1ll * jc[x] * invjc[y] % mod * invjc[x-y] % mod;
}
int qpow(int x,int y){
	int ans = 1;
	while(y){
		if(y & 1)	ans = 1ll * ans * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return ans;
}
int read(){
	char c = getchar();
	int x = 0;
	while(c < '0' || c > '9')		c = getchar();
	while(c >= '0' && c <= '9')		x = x * 10 + c - 48,c = getchar();
	return x;
}
int Add(int x,int y){
	x += y;
	return (x >= mod)?x - mod:x;
}
void treedp(int x){
	siz[x] = 1; 	
	int H = h[x] - h[fa[x]];
	dp[x][0] = 1;
//	cout<<x<<endl;
	if(lc[x]){
		fa[lc[x]] = x,treedp(lc[x]);
		memcpy(f,dp[x],sizeof(f));
		memset(dp[x],0,sizeof(dp[x]));
		for(int i = 0; i <= siz[x]; ++i){
			for(int j = 0; j <= siz[lc[x]]; ++j)
				dp[x][i + j] = Add(dp[x][i + j],1ll * f[i] * dp[lc[x]][j] % mod);
		}
		siz[x] += siz[lc[x]];
	}
	if(rc[x]){
		fa[rc[x]] = x,treedp(rc[x]);
		memcpy(f,dp[x],sizeof(f));
		memset(dp[x],0,sizeof(dp[x]));
		for(int i = 0; i <= siz[x]; ++i){
			for(int j = 0; j <= siz[rc[x]]; ++j)
				dp[x][i + j] = Add(dp[x][i + j],1ll * f[i] * dp[rc[x]][j] % mod);
		}
		siz[x] += siz[rc[x]];
	}
	memcpy(f,dp[x],sizeof(f));
	memset(dp[x],0,sizeof(dp[x]));
	for(int i = 0; i <= siz[x]; ++i){
		if(i > H)	break;
		for(int j = 0; j <= siz[x] - i; ++j){
			dp[x][i+j] = Add(dp[x][i+j],1ll * f[j] * C(siz[x] - j,i) % mod * jc[i] % mod * C(H,i) % mod);
		}
	}
}
int main(){
	jc[0] = invjc[0] = 1;
	int mx = 1e6;
	for(int i = 1; i <= mx; ++i)		jc[i] = 1ll * jc[i-1] * i % mod;
	invjc[mx] = qpow(jc[mx],mod-2);
	for(int i = mx - 1; i >= 0; --i)	invjc[i] = 1ll * invjc[i+1] * (i + 1) % mod;
	n = read(),cnt = read();
	for(int i = 1; i <= n; ++i)		h[i] = read();
	for(int i = 1; i <= n; ++i){
		int k = top;
		while(h[st[k]] > h[i])		k--;
		if(k)	rc[st[k]] = i;
		if(k != top)	lc[i] = st[k+1];
		st[++k] = i;
		top = k;
	}
	for(int i = 1; i <= n; ++i)		nrt[lc[i]] = nrt[rc[i]] = true;
	int rt = 0;
	for(int i = 1; i <= n; ++i){
		if(!nrt[i]){
			rt = i;
			break;
		}
	}
	treedp(rt);
//	cout<<dp[3][0]<<endl;
	printf("%d\n",dp[rt][cnt]);
	return 0;
}