笛卡尔树是一种二叉树,每一个节点由键值二元组 \((k,w)\) 构成, \(k\) 满足二叉搜索树的性质, \(w\) 满足堆的性质。
构建
我们可以用一个栈进行构建,假如我们想要求 \(k\) 满足二叉搜索树的性质,那么我们首先需要按 \(k\) 从小到大排序,然后一个一个插入;假如我们想要 \(w\) 满足小根堆的性质,那么我们每回在栈中找到第一个大于该点的点然后将这个点接在那个点的右儿子上,然后将那个点原先的右儿子接在这个点左儿子上。
int lson[N+5],rson[N+5];
int st[N+5],top;
void build(){
top=0;
for(int i=1;i<=n;++i){
int k=top;
while(k && a[st[k]]>a[i]) k--;
if(k) rson[st[k]]=i;
if(k<top) lson[i]=st[k+1];
st[++k]=i;
top=k;
}
}
应用
hdu6305 RMQ Similar Sequence
考虑到实数内随机不会重复,那么将这个序列考虑为一个排列。其实限制就是要求两棵笛卡尔树重构,那么重构的概率其实就是 \(\frac{1}{\prod_i siz_i}\) 相当于要求每个点是其子树中最大的点的概率。考虑权值是 \(\frac{n}{2}\) 所以答案就是相乘。感觉没啥意义
SPOJ PERIODNI
考虑建一棵权值为小根堆的笛卡尔树,这样的话,我们假如合并两个子树的答案就不会产生影响。我们设 \(dp_{i,j}\) 表示以 \(i\) 节点的子树中有 \(j\) 个互不相同点的方案数,然后发现转移就是一个树上的背包。我们再考虑这个节点贡献的计算,其实就是在一个长为 \(n\)(这个节点子树中最大编号与最小编号的差),宽为 \(m\) (这个节点的高度与父亲节点高度的差)的矩形中在排除已经选了的 \(x\) 个节点的情况下,选 \(k\) 个节点,方案其实就是 \({m \choose i}{n-x \choose i}i!\),然后就有转移:
\[f_{u,i+k} \leftarrow f_{u,i}*f_{v,k} \]\[f_{u,i+k} \leftarrow f_{u,k}*{m \choose i}{n-x \choose i}i! \]code
// #2616. SPOJ PERIODNI
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1005;
const int M=1e6;
const int mod=1e9+7;
int n,k;
int a[N],dp[N][N],F[N];
int ls[N],rs[N],siz[N];
int lson[N],rson[N],st[N],top=0;
int fac[M+5],inv[M+5];
int qpow(int x,int p){
int ans=1;
while(p){
if(p&1) ans=(ans*x)%mod;
x=x*x%mod;
p>>=1;
}
return ans;
}
void init(){
fac[0]=inv[0]=1;
for(int i=1;i<=M;i++) fac[i]=fac[i-1]*i%mod;
inv[M]=qpow(fac[M],mod-2);
for(int i=M-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
int C(int x,int y){
if(x<y) return 0;
return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
void build(){
for(int i=1;i<=n;i++){
int k=top;
while(k && a[st[k]]>a[i]) k--;
if(k) rson[st[k]]=i;
if(k<top) lson[i]=st[k+1];
st[++k]=i;
top=k;
}
}
void dfs(int x,int fa){
ls[x]=rs[x]=x;
siz[x]=1;
dp[x][0]=1;
if(lson[x]){
dfs(lson[x],x);
ls[x]=min(ls[x],ls[lson[x]]);
rs[x]=max(rs[x],rs[lson[x]]);
int y=lson[x];
for(int i=0;i<=siz[x] && i<=k;i++){
for(int j=0;i+j<=k && j<=siz[y];j++){
F[i+j]=(F[i+j]+dp[x][i]*dp[y][j]%mod)%mod;
}
}
siz[x]+=siz[y];
for(int i=0;i<=siz[x];i++){
dp[x][i]=F[i];
F[i]=0;
}
}
if(rson[x]){
dfs(rson[x],x);
ls[x]=min(ls[x],ls[rson[x]]);
rs[x]=max(rs[x],rs[rson[x]]);
int y=rson[x];
for(int i=0;i<=siz[x] && i<=k;i++){
for(int j=0;i+j<=k && j<=siz[y];j++){
F[i+j]=(F[i+j]+dp[x][i]*dp[y][j]%mod)%mod;
}
}
siz[x]+=siz[y];
for(int i=0;i<=siz[x];i++){
dp[x][i]=F[i];
F[i]=0;
}
}
int l=rs[x]-ls[x]+1;
int r=a[x]-a[fa];
for(int i=0;i<=siz[x] && i<=k;i++){
for(int j=0;j<=siz[x] && i+j<=k && i+j<=siz[x];j++){
F[i+j]=(F[i+j]+dp[x][i]*C(r,j)%mod*C(l-i,j)%mod*fac[j]%mod)%mod;
}
}
for(int i=0;i<=siz[x];i++){
dp[x][i]=F[i];
F[i]=0;
}
}
signed main(){
init();
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build();
dfs(st[1],0);
printf("%lld",dp[st[1]][k]);
}
hdu4125Moles
考虑其实就是建一棵二叉搜索树,我们考虑笛卡尔树 \(O(n)\) 构建,那么就是让权值满足二叉搜索树的性质,下表满足小根堆的性质。然后建完跑 \(kmp\) 就可以了。
code
// #2616. SPOJ PERIODNI
#include<bits/stdc++.h>
using namespace std;
const int N=5*1e6;
int n,m,b[N];
struct asd{
int w,id;
}a[N+5];
int st[N+5],top;
int lson[N+5],rson[N+5];
char s[N+5];
int dt[N*10],tp=0;
bool amp(asd a,asd b){
return a.w<b.w;
}
void dfs(int x){
dt[++tp]=b[x]%2;
if(lson[x]){
dfs(lson[x]);
dt[++tp]=b[x]%2;
}
if(rson[x]){
dfs(rson[x]);
dt[++tp]=b[x]%2;
}
}
void build(){
top=0;
for(int i=1;i<=n;i++){
int k=top;
while(k && st[k]>a[i].id) k--;
if(k) rson[st[k]]=a[i].id;
if(k<top) lson[a[i].id]=st[k+1];
st[++k]=a[i].id;
top=k;
}
}
int nex[N+5];
void get_next(){
nex[1]=0;
int j=0;
for(int i=2;i<=m;i++){
while(j>0 && s[i]!=s[j+1] ) j=nex[j];
if(s[i]==s[j+1]) j++;
nex[i]=j;
}
}
inline int read(){
int x(0);bool f(0);char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()) f^=ch=='-';
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f?x=-x:x;
}
signed main(){
int T;
T=read();
for(int p=1;p<=T;p++){
int rt=0;
tp=0;
n=read();
for(int i=1;i<=n;i++){
b[i]=read();
a[i].w=b[i];
a[i].id=i;
}
scanf("%s",s+1);
sort(a+1,a+n+1,amp);
build();
m=strlen(s+1);
dfs(st[1]);
get_next();
int ans=0;
int j=0;
for(int i=1;i<=tp;i++){
while(j && (dt[i]!=(s[j+1]-'0') || j==n )) j=nex[j];
if(dt[i]==(s[j+1]-'0')) j++;
if(j==m) ans++;
}
printf("Case #%d: %d\n",p,ans);
for(int i=0;i<=n;i++) lson[i]=rson[i]=0;
}
}
/*
摩尔线不必按摩尔数排序。
每只鼹鼠都应该生活在自己挖的洞里,每只鼹鼠只挖一个洞。
这条线上的第一个鼹鼠挖第一个洞,洞里有一条通向地面的通道。
然后其他鼹鼠穿过那个通道,挖更多的洞和通道。
一个洞最多可以有三个由通道连接的邻居,一个在上层,另外两个洞在下层,分别位于左侧和右侧。
当鼹鼠到达一个洞时,如果它的数量小于洞主人的数量,它会去左下洞(或者挖一个左下洞,当没有左下洞时留在那里),
否则它会去右下洞(或挖一个右下洞,没有右下洞时呆在那里)。
由于其出色的能力和精心设计的布局,这些孔和通道不会相互交叉。
老鼠杰瑞是那些鼹鼠的朋友。
他来拜访他们,并为每一个鼹鼠准备礼物。
有一条规则是,数量较小的鼹鼠必须比数量较大的鼹鼠更早得到礼物。
杰瑞从地上开始。他穿过洞和通道去送礼物。
在送出所有礼物后,他又回到了地上。
在鼹鼠的世界里,有趣的是,奇数的鼹鼠是雄性,其他的是雌性。
当到达一个洞时,Jerry记下洞主人的性别(0代表女性,1代表男性)。
当他回到地面时,他将获得0-1的顺序。现在他想计算“和谐值”。
和声值表示给定“和声串”在上述序列中的出现次数。事件可能会重叠。
*/