P3600 随机数生成器

非常好的期望套路

[Luogu3600]

有一个长度为n的整数列\(a_1 , a_2 \dots a_n\) ,每个元素在\([1, x]\)中的整数中均匀随机生成。
• 有q个询问,第i个询问的结果是下标在\([l_i , r_i ]\)的元素的最小值。
•求这q个询问结果的最大值的期望\(,mod 666623333\)

原题解

全概率公式\(E(x)=\sum_{i=0}^\infty P(x\geq i)\)

于是对于每一个\(i\),我们只要计算出\(P(ans\geq i)\)即可

然而因为这里要计算的是最小值,如果是大于等于很不方便,于是我们考虑转化为

\(1.P(ans\geq i)=1-P(ans<i)\)

也就是意味着每一个区间中都至少有一个数小于等于\(i-1\)

有一些区间相互包含,这里是求最小值的最大,转化为大的区间没用

如果一个数小于等于\(i-1\)那么它会对一些区间产生贡献,且这些区间的编号肯定是连续的。

\(2.\)考虑用点去覆盖区间,即每个点能覆盖一些区间,且覆盖的概率为\(p=\frac{i-1}{x},\)求所有区间都被覆盖的概率

\(l[i]\)\(i\)能覆盖到的最左边的区间,\(r[i]\)同理,\(f[i]\)\(r[i]\)及其之前的区间都被覆盖的概率,那么有

\(f[i]=p*(\sum_{r[j]\geq l[i]-1} f[j]*(1-p)^{i-j-1})\)

\(3.\)即枚举上一个选的点\(j\),那么必须有\(r[j]\geq l[i]-1\)才能保证\(r[i]\)及其之前的区间都被覆盖,又因为\(j\)是上一个被选的,所以\([j+1,i-1]\)都没有被覆盖。

最后的答案就是\(\sum_{r[i]=m} f[i]*(1-p)^{n-i}\),就是枚举最后一个被选的点,然后因为它是最后一个,所以\([i+1,n]\)都没被覆盖

双指针可以优化到\(O(n)\)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define Debug(x) cout<<#x<<"="<<x<<endl
using namespace std;
typedef long long LL;
const int INF=1e9+7;
inline LL read(){
    register LL x=0,f=1;register char c=getchar();
    while(c<48||c>57){if(c=='-')f=-1;c=getchar();}
    while(c>=48&&c<=57)x=(x<<3)+(x<<1)+(c&15),c=getchar();
    return f*x;
}

const int MAXN=2005;
const int mod=666623333;

struct Node{
    int l,r;
    inline friend bool operator < (Node a,Node b){
        if(a.l==b.l) return a.r>b.r;
        return a.l<b.l;
    }
}a[MAXN];
int st[MAXN],top;

int l[MAXN],r[MAXN],f[MAXN],invp[MAXN];
int n,m,x,ans;

inline int add(int x,int y){x+=y;return x>mod?x-mod:x;}
inline int dec(int x,int y){x-=y;return x<0?x+mod:x;}
inline int mul(LL x,int y){x*=y;return x>mod?x%mod:x;}

inline int qpow(int a,int b){
    int res=1;
    while(b){
        if(b&1) res=mul(res,a);
        a=mul(a,a);
        b>>=1;
    }
    return res;
}

int main(){
    n=read(),x=read(),m=read();
    for(int i=1;i<=m;i++) a[i].l=read(),a[i].r=read();
    sort(a+1,a+m+1);
    for(int i=1;i<=m;i++){
        while(top&&a[i].r<=a[st[top]].r) top--;
        st[++top]=i;
    }
    m=top;
    for(int i=1;i<=m;i++) a[i]=a[st[i]];
    int head=1,tail=0;
    for(int i=1;i<=n;i++){
        while(tail<m&&a[tail+1].l<=i) tail++;
        while(head<=tail&&a[head].r<i) head++;
        l[i]=head,r[i]=tail;
    }
    for(int i=1;i<=x;i++){
        int sum=1,p=mul(dec(i,1),qpow(x,mod-2)),
            notp=dec(1,p),fp=qpow(notp,mod-2),leip=1;
        invp[0]=1,f[0]=1;
        for(int i=1;i<=n;i++) invp[i]=mul(invp[i-1],fp);
        for(int i=1,j=0;i<=n;i++){
            while(j<i&&r[j]<l[i]-1) sum=dec(sum,mul(f[j],invp[j])),j++;
            f[i]=mul(sum,mul(leip,p)),leip=mul(leip,notp);
            sum=add(sum,mul(f[i],invp[i]));//这样能够实现前缀和的优化
        }
        int tot=0;leip=1;
        for(int i=n;i&&r[i]==m;i--,leip=mul(leip,notp))
            tot=add(tot,mul(f[i],leip));
        ans=add(ans,dec(1,tot));
    }
    printf("%d\n",ans);
}

转载于:https://www.cnblogs.com/lizehon/p/10570481.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值