[SPOJ][BZOJ2616]PERIODNI
\[\color{red}{\text{校长者,真神人也,左马桶,右永神,会执利笔破邪炁,何人当之?}} \\
\begin{array}{|}
\hline
\color{pink}{\text{The principal is really a god}} \\
\color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\
\color{pink}{\text{holding a sharp pen to pierce the truth}} \\
\color{pink}{\text{Who can resist him? }} \\
\hline
\end{array} \\
\begin{array}{|}
\hline
\color{green}{\text{校長は本当に神であり、左側にトイレ、右側にヨンシェンがあり}} \\
\color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\
\hline
\end{array} \\
\begin{array}{|}
\hline
\color{lightblue}{\text{Le principal est vraiment un dieu}} \\
\color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\
\color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\
\color{lightblue}{\text{Qui peut lui résister ? }} \\
\hline
\end{array} \\
\begin{array}{|}
\hline
\color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\
\color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\
\color{purple}{\text{der einen spitzen Stift hält}} \\
\color{purple}{\text{um die Wahrheit zu durchdringen.}} \\
\color{purple}{\text{Wer kann ihm widerstehen? }} \\
\hline
\end{array} \\
\begin{array}{|}
\hline
\color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\
\color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\
\hline
\end{array} \\
\color{red}{\text{对曰:“无人,狗欲当之,还请赐教!”}} \\
\newcommand\bra[1]{\left({#1}\right)}
\newcommand\Bra[1]{\left\{{#1}\right\}}
\newcommand\dx[0]{\text{dx}}
\newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}}
\newcommand\down[2]{{#1}^{\underline{#2}}}
\newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor}
\newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil}
\newcommand\lcm[0]{\operatorname{lcm}}
\newcommand\set[1]{\left\{{#1}\right\}}
\]
壹、题目描述 ¶
贰、题解 ¶
二维直方图,可以考虑使用笛卡尔树。平凡地,按照 \((i,h_i)\) 建立笛卡尔树。这样有什么好处呢?显然一个点的左右儿子可以任意放车而不会影响彼此。
接下来显然是树 \(\rm DP\) 的过程,考虑设 \(dp(i,j)\) 表示在 \(i\) 的子树中放 \(j\) 个车的方案数,接下来我们考察,如何从左右儿子到当前点 \(u\) ?我们不妨设 \(l,r\) 表示左右儿子,\(H\) 表示 \(h_u-h_{fa(u)}\),\(W\) 表示 \(siz_u\),即扩展出的矩形宽度。
先考虑左右儿子的任意匹配,记
\[tmp(j)=\sum_{x=0}^jdp(ls,x)\times dp(rs,j-x)
\]
那么,\(tmp(j)\) 即表示在儿子所管辖的范围中放了 \(j\) 个车的方案数,接下来考虑在 \(u\) 所管辖的 \(H\times W\) 的矩阵中放置的方案数,显然有
\[dp(i,j)=\sum_{x=0}^j tmp(j-x)\times {W-j+x\choose x}\times\down{H}{x}
\]
总复杂度 \(\mathcal O(NK^2)\),不知道为什么本机测试 \(\rm TLE\) 了......
叁、参考代码 ¶
# include <cstdio>
# include <algorithm>
# include <cstring>
using namespace std;
# define NDEBUG
# include <cassert>
namespace Elaina {
# define rep(i, l, r) for(int i=(l), i##_end_=(r); i<=i##_end_; ++i)
# define drep(i, l, r) for(int i=(l), i##_end_=(r); i>=i##_end_; --i)
# define fi first
# define se second
# define mp(a, b) make_pair(a, b)
# define Endl putchar('\n')
# define mmset(a, b) memset(a, b, sizeof (a))
# define mmcpy(a, b) memcpy(a, b, sizeof (a))
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair <int, int> pii;
typedef pair <ll, ll> pll;
template <class T> inline T fab(T x) { return x<0? -x: x; }
template <class T> inline void getmin(T& x, const T rhs) { x=min(x, rhs); }
template <class T> inline void getmax(T& x, const T rhs) { x=max(x, rhs); }
template <class T> inline T readin(T x) {
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template <class T> inline void writc(T x, char s='\n') {
static int fwri_sta[1005], fwri_ed=0;
if(x<0) putchar('-'), x=-x;
do fwri_sta[++fwri_ed]=x%10, x/=10; while(x);
while(putchar(fwri_sta[fwri_ed--]^48), fwri_ed);
putchar(s);
}
} using namespace Elaina;
const int mod=1e9+7;
const int maxn=500;
const int maxk=500;
const int maxh=1000000;
inline int qkpow(int a, int n) {
int ret=1;
for(; n>0; n>>=1, a=1ll*a*a%mod)
if(n&1) ret=1ll*ret*a%mod;
return ret;
}
int finv[maxh+5], fac[maxh+5];
inline void prelude() {
finv[0]=fac[0]=1;
rep(i, 1, maxh) fac[i]=1ll*fac[i-1]*i%mod;
finv[maxh]=qkpow(fac[maxh], mod-2);
for(int i=maxh-1; i; --i) finv[i]=1ll*finv[i+1]*(i+1)%mod;
}
inline int down(int n, int x) {
if(n==0 && x==0) return 1;
if(x>n) return 0;
return 1ll*fac[n]*finv[n-x]%mod;
}
inline int C(int n, int m) {
if(n<m || n<0 || m<0) return 0;
return 1ll*down(n, m)*finv[m]%mod;
}
int n, k;
int h[maxn+5];
inline void input() {
n=readin(1), k=readin(1);
rep(i, 1, n) h[i]=readin(1);
}
int ls[maxn+5], rs[maxn+5], rt;
int stk[maxn+5], ed;
inline void build() {
rep(i, 1, n) {
while(ed && h[stk[ed]]>h[i]) ls[i]=stk[ed--];
if(!ed) rt=i;
else rs[stk[ed]]=i;
stk[++ed]=i;
}
}
int fa[maxn+5], siz[maxn+5];
void dfs1(int u, int par) {
fa[u]=par;
if(ls[u]) dfs1(ls[u], u);
if(rs[u]) dfs1(rs[u], u);
siz[u]=siz[ls[u]]+siz[rs[u]]+1;
}
int dp[maxn+5][maxk+5], tmp[maxk+5];
void dfs2(int u) {
if(ls[u]) dfs2(ls[u]);
if(rs[u]) dfs2(rs[u]);
int H=h[u]-h[fa[u]], l=ls[u], r=rs[u];
memset(tmp, 0, k+1<<2);
rep(j, 0, k) rep(x, 0, j)
tmp[j]=(tmp[j]+1ll*dp[l][x]*dp[r][j-x]%mod)%mod;
rep(j, 0, k) rep(x, 0, j) {
dp[u][j]=(dp[u][j]+1ll*tmp[j-x]*C(siz[u]-j+x, x)%mod*down(H, x)%mod)%mod;
} dp[u][0]=1;
}
signed main() {
prelude();
input();
build();
dfs1(rt, 0);
dp[0][0]=1; // special !
dfs2(rt);
writc(dp[rt][k]);
return 0;
}
肆、关键 の 地方 ¶
遇到直方图的时候,可以考虑使用笛卡尔树(或者其思想),笛卡尔树的本质,就是将直方图划分成多个子矩阵解决,这在很多情况下是十分便于处理的。