1. 程式人生 > 其它 >【洛谷】[COCI2008-2009#4] PERIODNI

【洛谷】[COCI2008-2009#4] PERIODNI

題目連結:https://www.luogu.com.cn/problem/P6453

因為一行中如果斷開的話就不算在同一行了,所以我們可以一行一行來考慮。
這樣的話每層都會截出來一個最大的矩形,這有點向一個樹形結構,可以用笛卡爾樹(還是有點難想的)

這裡提一下,對於2312131這樣的棋盤
三個1本來應該是同等級別的,但是在樹上卻有父子關係,這不要緊,因為它們之間的高的差為0

記f[i][j]表示以i為代表的區域內放j個棋的方案數,sz[u]表示以u為根的子樹大小
顯然,對於節點u,其矩陣的長為sz[u],寬為h[u]-h[fa[u]]。
所以我們先跑一個樹上揹包,合併所有子樹的情況,再單獨討論該節點的區域內的放置情況。
假設一共放j個棋,在該區域內放k個棋則方案數為\(\dbinom{sz[u]-j+k}{k} \dbinom{h[u]-h[fa[u]]}{k} k!\)

本來矩陣的長為sz[u],但是要在子節點的範圍內先放上j-k個棋,佔用了j-k列,故還有sz[u]-j+k列能用來放

#include<bits/stdc++.h>
using namespace std;
const int N = 505, M = 1e6 + 5, P = 1e9 + 7;
int n, m, top, stk[N], inv[M], invfac[M], fac[M];
int h[N], f[N][N], sz[N], son[N][2];
inline void init(int n){
	inv[1] = fac[1] = fac[0] = invfac[1] = invfac[0] = 1;
	for (int i = 2; i <= n; ++i){
		inv[i] = 1ll * (P - P / i) * inv[P % i] % P;
		invfac[i] = 1ll * invfac[i - 1] * inv[i] % P;
		fac[i] = 1ll * fac[i - 1] * i % P;
	}
}
inline int C(int x, int y){
	if (x < 0 || y < 0 || x > y) return 0;
	return 1ll * fac[y] * invfac[y - x] % P * invfac[x] % P;
}
void dfs(int u, int fath){
	f[u][0] = 1, sz[u] = 1;
	int hi = h[u] - h[fath];
	for (int i = 0; i <= 1; ++i){
		int v = son[u][i];
		if (!v) continue;
		dfs(v, u);
		for (int j = min(sz[u], m); j >= 0; --j)
			for (int k = 1; k <= min(sz[v], m - j); ++k)
				f[u][j + k] = (f[u][j + k] + 1ll * f[u][j] * f[v][k]) % P;
		sz[u] += sz[v];
	}
	for (int i = min(sz[u], m); i >= 1; --i)
		for (int j = 1; j <= min(i, hi); ++j)
			f[u][i] = (f[u][i] + 1ll * f[u][i - j] * C(j, sz[u] - i + j) % P * C(j, hi) % P * fac[j]) % P;
}
int main(){
	init(M - 5);
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		scanf("%d", h + i);
	for (int i = 1; i <= n; ++i){
		while (top && h[i] < h[stk[top]]) son[i][0] = stk[top--];
		if (top) son[stk[top]][1] = i;
		stk[++top] = i;
	}
	dfs(stk[1], 0);
	printf("%d\n", f[stk[1]][m]);
	return 0;
}