peiwenjun's blog 没有知识的荒原

HDU6317 Segment 题解

题目描述

定义一棵树为广义线段树,当且仅当对于区间 \([l,r]\) ,它的左右儿子分别为 \([l,x]\)\([x+1,r]\)\(l\le x<r\) ),区间 \([i,i]\) 为叶子。

给定 \(n\)等概率随机一棵以 \([1,n]\) 为根的广义线段树。

\(q\) 次询问,对于给定区间 \([l,r]\) ,将 \([l,r]\) 定位到广义线段树上,求期望访问多少节点,对 \(998244353\) 取模。

数据范围

  • \(1\le n,q\le 10^6,1\le l\le r\le n\)

时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{32MB}\)

分析

区间 \(\texttt{dp}\) 状态数不低于 \(\mathcal O(n^2)\) ,根本没有前途。

根据期望的线性性,我们需要求每个区间被经过的概率,然后相加。

考虑另一种生成广义线段树的方式:

\(\forall 1\le i\lt n\) ,令 \(i\)\(i+1\) 之间有一个间隔(后面称为间隔 \(i\) ),每次等概率随机一个间隔,将它所在区间从这里切断,然后将这个间隔删除。

因此区间 \([l,r]\) 在线段树上出现,当且仅当间隔 \(l-1\) 和间隔 \(r\)\(\forall i\in [l,r-1]\) ,间隔 \(i\) 之前出现。

记区间长度为 \(len\)如果 \(l\neq 1\and r\neq n\) ,那么区间 \([l,r]\) 在线段树上出现概率为 \(\frac 2{len(len+1)}\)

对于 \(l=1\)\(r=n\) 的边界情况,我们只需保证另一个边界最先出现,出现概率为 \(\frac1{len}\)

一个简单的想法是将所有与 \([l,r]\) 有交的区间的出现概率加起来作为答案,但显然会算重。

算重的情况是,如果某个区间 \(x\) 和它的父节点 \(fa_x\) 都被 \([l,r]\) 完全包含,那么区间 \(x\) 不会被访问到。

因此我们需要减掉被 \([l,r]\) 完全包含的父子对 \((x,fa_x)\) 出现概率。

\(fa_x\) 处统计贡献,由于线段树每个节点恰有两个儿子,所以\(fa_x\) 为父节点的父子对的出现概率之和为 \(2\) 倍的 \(fa_x\) 出现概率。(注意我们不关心 \(fa_x\) 的两个子节点分别是什么)

至此原问题被拆成了两个子问题:

  • 求所有与 \([l,r]\) 相交的区间出现概率之和。
  • 求所有被 \([l,r]\) 包含的非叶区间出现概率之和。

对于第一个问题,容斥成所有区间减去 \(l\) 左侧的区间减去 \(r\) 右侧的区间。

\(f_x=\sum_{i=1}^x\frac 1i+(x-i)\frac 2{i(i+1)}\) ,则答案为 \(2n-1-f_{l-1}-f_{n-r}\)

\(x-i\) 拆成 \((x+1)-(i+1)\) ,化简得 \(f_x=2x-h_x\) ,其中 \(h_x\) 为调和级数前缀和。


对于第二个问题,先特判 \(l=1\and r=n\)

枚举长度 \(i\) ,被 \([l,r]\) 包含且长为 \(i\) 的区间有 \(len-i+1\) 个。

如果 \(l=1\or r=n\) ,再减掉多算的贡献即可。

\[ans=\sum_{i=2}^{len}(len-i+1)\cdot\frac 2{i(i+1)}+([l=1]+[r=n])\sum_{i=2}^{len}(\frac 1i-\frac 2{i(i+1)})\\ =len+\sum_{i=1}^{len}\frac 2{i+1}+([l=1]+[r=n])\sum_{i=1}^{len}(\frac 1i-\frac 2{i(i+1)})\\ =len+2-h_{len+1}+([l=1]+[r=n])(h_{len}-2+\frac 2{len+1})\\ \]

时间复杂度 \(\mathcal O(n+q)\)

注意杭电评测机读写效率非常低下, freadfwrite 一个都不能少。

#include<bits/stdc++.h>
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<23,stdin),p1==p2)?EOF:*p1++)
using namespace std;
const int maxn=1e6+5,mod=998244353;
int n,q;
int b[maxn],p[maxn],inv[maxn];
int h[maxn];
char buf[1<<23],*p1=buf,*p2=buf,obuf[1<<23],*O=obuf;
inline int read()
{
    int q=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) q=10*q+ch-'0',ch=getchar();
    return q;
}
inline void write(int x)
{
    if(x>=10) write(x/10);
    *O++=x%10+'0';
}
inline int qpow(int a,int k)
{
    int res=1;
    for(;k;a=1ll*a*a%mod,k>>=1) if(k&1) res=1ll*res*a%mod;
    return res;
}
inline void init(int n)
{
    inv[1]=1;
    for(int i=2,cnt=0;i<=n;i++)
    {
        if(!b[i]) p[++cnt]=i,inv[i]=qpow(i,mod-2);
        for(int j=1;j<=cnt&&i*p[j]<=n;j++)
        {
            b[i*p[j]]=1,inv[i*p[j]]=1ll*inv[i]*inv[p[j]]%mod;
            if(i%p[j]==0) break;
        }
    }
    for(int i=1;i<=n;i++) h[i]=(h[i-1]+inv[i])%mod;
}
inline int f(int x)
{
    return 2*x-h[x];
}
int main()
{
    n=read(),q=read(),init(n+1);
    while(q--)
    {
        int l=read(),r=read(),len=r-l+1;
        if(l==1&&r==n)
        {
            printf("1\n");
            continue;
        }
        int a=(2*n-1-f(l-1)-f(n-r))%mod;
        int b=((len+2-2ll*h[len+1])+(l==1||r==n)*(h[len]-2+2ll*inv[len+1]))%mod;
        write((3ll*mod+a-2*b)%mod);
        fwrite(obuf,O-obuf,1,stdout),putchar('\n'),O=obuf;
    }
    return 0;
}

posted on 2023-06-14 19:00  peiwenjun  阅读(12)  评论(0)    收藏  举报

导航