题目链接:1008 RMQ Similar Sequence
题目大意
rmq(a, l, r)表示a[l-r]中最大值的下标,如果有相同的数字,取下标小的
定义rmq相似,如果两个数组a, b长度相同,对于所有[l, r],都有rmq(a, l, r) == rmq(b, l, r)则abrmq相似
现在给你一个数组a, 有一个数组b长度与a相同,且每一个元素都是从[0, 1]之间独立地随机选出的一个实数,定义b的权值为:1.如果ab相似,权值为所有b中元素的和(
∑ni=1bi
∑
i
=
1
n
b
i
)2. 否则为0
求b权值的期望(mod 1e9+7)
思路
笛卡尔树:
笛卡尔树是一棵二叉树,树的每个节点有两个值,一个为key,一个为value。光看key的话,笛卡尔树是一棵二叉搜索树,每个节点的左子树的key都比它小,右子树都比它大;光看value的话,笛卡尔树有点类似堆,根节点的value是最小(或者最大)的,每个节点的value都比它的子树要小(或者大)。
以根节点最大为例
求笛卡尔树可以使用单调栈,栈中存储的是笛卡尔树的右子节点链,根节点在栈底,从数组第一个元素开始依次插入笛卡尔树,插入方法是从右子链从下往上查找,直到找到第一个满足value大于当前元素的节点,将新节点作为其右节点插入,原来的右子树作为新节点的左子树
解题思路
将元素下标当做key,元素大小当做value,构造一个笛卡尔树,rmq相似等同与两个数组生成的笛卡尔树相似
用一个单调栈可以
O(n)
O
(
n
)
地求出笛卡尔树,rmq相似的概率即为笛卡尔树形状一样的概率,也就是每个节点一一对应,对于笛卡尔树上的节点
nodei
n
o
d
e
i
,以它为根节点的子树的节点个数为
sizei
s
i
z
e
i
,两棵树相似那么每棵子树也相似,子树
nodei
n
o
d
e
i
相似的概率为
1sizei
1
s
i
z
e
i
(即子树对应区间内,b[i]为最大值的概率为
1sizei
1
s
i
z
e
i
)
最后将每个节点的概率相乘就是一棵树与a树相似的概率了,为
∏ni=11sizei
∏
i
=
1
n
1
s
i
z
e
i
, 而这棵所有节点元素和的期望为
n2
n
2
, 所以权值期望为
n2∏ni=11sizei
n
2
∏
i
=
1
n
1
s
i
z
e
i
, 取mod,因为有除法所以要预处理出1到maxn的逆元
代码
Status | Time | Memory | Length | Lang |
---|---|---|---|---|
Accepted | 920MS | 99192K | 1211B | G++ |
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn = 1e6 + 100, mod = 1e9+7;
typedef pair<int, int> P;
typedef long long ll;
int s[maxn], top;
P a[maxn];
ll inv[maxn];
int T, n;
int l[maxn], r[maxn];
int siz[maxn];
int dfs(int rt)
{
siz[rt] = 1;
if(l[rt]) siz[rt] += dfs(l[rt]);
if(r[rt]) siz[rt] += dfs(r[rt]);
return siz[rt];
}
int main()
{
inv[1]=1;
for(int i=2; i<maxn; ++i) inv[i]=inv[mod%i]*(mod-mod/i)%mod;//O(n)预处理逆元
for(scanf("%d", &T); T; --T)
{
scanf("%d", &n);
for(int i=1; i<=n; ++i)
{
scanf("%d", &a[i].first);
a[i].second = -i;//方便比较,如果值相同,视下标小的更大
}
for(int i=1; i<=n; ++i) l[i] = r[i] = 0;
int top = 0;
for(int i=1; i<=n; ++i)
{
int k = top;
while(k && a[s[k-1]] < a[i]) --k;//求出第一个大于新元素的节点
if (k) r[s[k-1]] = i; //新节点作为该节点的右儿子
if(k < top) l[i] = s[k];//该节点原来的右子树作为新节点的左子树
s[k++] = i;
top = k;
}
int rt = s[0];//笛卡尔树根节点
dfs(rt);
ll ans = inv[2]*n%mod;
for(int i=1; i<=n; ++i)
{
ans *= inv[siz[i]];
ans %= mod;
}
printf("%lld\n", ans);
}
return 0;
}