JeremyGuo膜你赛 t3 azui

题意:

你有一个长度为 n 的序列,其中第 i 个位置上写了一个浮点数 pipi,表示你如果 在当前位置,那么你有 pi 的概率向右走一步,反之(1−pi)的概率向反方向走一步。 定义一个游戏获胜的概率为: 一个序列,你一开始站在 1 的位置上,按照上述规则进行移动,如果从序列右端 移动出去了,那么就算获胜,如果中途从左端移出,就算失败,问经过了任意次 的移动后获胜的概率。 现在有一长度为 n 的序列,m 个操作,操作分为以下两种: 1、修改一个位置上的 pi 2、询问如果将某一段[l,r]提取出来单独做一个游戏,获胜的概率是多少?

第一行给出两个数 n,m 表示序列的长度和操作的数量 接下来 n 行每行包含两个数 Ai, Bi 这用来表示 pi=Ai/Bi 其中第 i 个表示 pi 接下来 m 行每行第一个数表示操作类型 若为 1,那么接下来三个数分别表示修改的位置和新的 pi=Ai/Bi 若为 2,那么接下来两个证书 L 和 R,表示进行游戏的区间。 

输出的行数应该和操作 2 的数量相同,每行一个整数,最后的答案模 9999991 (质数)输出——就是说分数用整数表示(别告诉我你不知道怎么搞)。 好吧:pi=Ai*Bi^(-1)=Ai*Bi^(MOD-2)然后你把 pi 当成浮点数就行了,不用管别 的

 保证 1≤ n,m ≤ 200000,并且对于每个点的概率 Ai/Bi 保证 Ai≤Bi≤1000

题解

这道题是一道好题,正解是线段树。

可以发现,这道题要求单点修改,区间查询,这些线段树都可以轻松做到,只要你解决了如何维护这颗线段树,这道题也就相当于解决了

维护线段树的难度在于区间合并,对于处理这个问题,有两种思路,暴力列式化简和列方程

这里主要介绍一下来自EnderGZM的列方程做法:

首先对于线段树的每个区间,维护两个值,一个是最左端走出最右端的Plr,另一个是从最右端走出最左端Prl

为了方便,对于合并的另一个相邻区间的这两个值,我们用Qlr和Qrl表示,考虑如何列出方程,见下图


用P[A]表示从A点走出左区间的概率,P[B],P[C]同理

那么可以得出如下方程:

P[A]=Plr*P[C] 从A走到C,再从C走出

P[C]=Qlr+(1-Qlr)*P[B] 从C走出分两种情况,直接走出与往回走到B再走出

P[B]=(1-Prl)*P[C] 从B走到C,再从C走出

于是就可以通过上述方程解出P[A],这也即是合并后区间的Plr

Prl同理可得

这里直接把结论给出来:

合并后的区间

Plr=plr*qlr/(1-(1-qlr)*(1-prl))

Prl=prl*qrl/(1-(1-qlr)*(1-prl))

于是这道题就可以做了,虽然是数据结构,但是代码也并不难实现,只有不到100行

code:

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
 
using namespace std;
const int MAXN=200005;
const int mod=9999991;
int n,m,t,A,B,P[MAXN];
struct node{
    int s,e,plr,prl;
}tree[MAXN<<2];
#define lson i<<1
#define rson i<<1|1
int inv[mod+5];
 
inline void prepare(int n){
    inv[0]=inv[1]=1;
    for(int i=2;i<=n;i++)
        inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    return;
}
inline void Read(int &Ret){
    char ch;bool flag=0;
    for(;ch=getchar(),!isdigit(ch);)if(ch=='-')flag=1;
    for(Ret=ch-'0';ch=getchar(),isdigit(ch);Ret=Ret*10+ch-'0');
    flag&&(Ret=-Ret);
}
inline void pushup(int i){
    int q = 1ll*(1ll-tree[lson].prl+mod)%mod*(1ll-tree[rson].plr+mod)%mod;
    tree[i].plr = 1ll*tree[lson].plr*tree[rson].plr%mod*inv[(1ll-q+mod)%mod]%mod;
    tree[i].prl = 1ll*tree[lson].prl*tree[rson].prl%mod*inv[(1ll-q+mod)%mod]%mod;
}
void build(int i,int s,int e){
    tree[i].s=s; tree[i].e=e;
    if(s==e)
    {
        Read(A); Read(B);
        tree[i].plr=1ll*A*inv[B]%mod;
        tree[i].prl=(mod+1ll-tree[i].plr)%mod;
        return;
    }
    int mid=(s+e)>>1;
    build(lson,s,mid);
    build(rson,mid+1,e);
    pushup(i);
}
int pos,cA,cB;
void update(int i){
    if(tree[i].s==tree[i].e)
    {
        tree[i].plr=1ll*cA*inv[cB]%mod;
        tree[i].prl=(1-tree[i].plr+mod)%mod;
        return;
    }
    if(pos<=tree[lson].e) update(lson);
    else update(rson); pushup(i);
}
int s,e,Plr,Prl;
inline void Merge(int i){
    Plr=1ll*Plr*tree[i].plr%mod*inv[((1ll-(1ll-tree[i].plr)*(1ll-Prl)%mod)+mod)%mod]%mod;
    Prl=1ll*Prl*tree[i].prl%mod*inv[((1ll-(1ll-tree[i].plr)*(1ll-Prl)%mod)+mod)%mod]%mod;
}
void Query(int i){
    if(s<=tree[i].s&&tree[i].e<=e)
        {Merge(i); return;}
    if(s <= tree[lson].e) Query(lson);
    if(e >= tree[rson].s) Query(rson);
}
int main()
{
    Read(n); Read(m);
    prepare(mod); build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        Read(t);
        if(t==1)
        {
            Read(pos); Read(cA); Read(cB);
            update(1);
        }
        else
        {
            Read(s); Read(e);
            Plr=Prl=1; Query(1);
            printf("%d\n",(Plr+mod)%mod);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值