2018 Multi-University Training Contest 1 1008 RMQ Similar Sequence 笛卡尔树 概率

题目链接: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 , 所以权值期望为 n2ni=11sizei n 2 ∏ i = 1 n 1 s i z e i , 取mod,因为有除法所以要预处理出1到maxn的逆元

代码

StatusTimeMemoryLengthLang
Accepted920MS99192K1211BG++
#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值