[ Luogu 3924 ] 康纳的线段树
\(\\\)
\(Description\)
现在有一个线段树维护长为\(N\)的数列,实现方式是\(mid=((l+r)>>1)\),支持区间加,节点维护区间和。
共有\(M\)次区间加,每次区间加之后询问,选一条从根到任意叶子节点的链,其上的节点维护的区间和之和的期望。
- \(N,M\le 10^6\)
\(\\\)
\(Solution\)
根据期望的线性性,开始的想法是维护每一个节点在选中的链上的概率,每次修改把修改乘上概率,复杂度\(\text O(NlogN)\),卡\(T\)三个点。
然后仔细回想每一次增加的过程。对一个叶子增加,显然对他的所有祖先都会有贡献,假如一个节点深度为\(k\),那么它被选在链里的概率就是\(\frac 1{2^k}\) ,所以一个叶子节点 \(i\) 从自己开始所有祖先的概率和为
\[\sum_{i=0}^{deep[i]} \frac 1{2^i}=2-\frac 1{2^{deep[i]}}
\]
因为期望的线性性,所以这一个叶节点增加,所有祖先会对总期望加上选择概率\(\times\)增量的答案,不妨直接记录每个叶节点及其祖先节点在这个节点增加时的概率和,这样就可以直接统计一个节点对答案的增量了。
然后区间加的时候区间的增量都相同,所以把概率求一个前缀和,然后总增量就是区间概率和\(\times\)单点增量了。
这题邪在需要\(long\ double\),而且用 \(printf\) 输出 \(\%lf\) 是非常慢的,所以要先强转\(long\ long\),然后注意转换的时候要设\(eps\)。
\(\\\)
\(Code\)
#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1000010
#define R register
#define gc getchar
#define mid ((l+r)>>1)
using namespace std;
typedef long long ll;
typedef long double ld;
inline ll rd(){
ll x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
ll n,m;
ld ans,bin[30],p[N];
const ld eps=1e-9;
void dfs(ll dp,ll l,ll r){
if(l==r){p[l]=dp;return;}
dfs(dp+1,l,mid);
dfs(dp+1,mid+1,r);
}
int main(){
n=rd(); m=rd(); bin[0]=rd();
dfs(0,1,n);
for(R ll i=1;i<=30;++i) bin[i]=bin[i-1]/2;
for(R ll i=1;i<=n;++i){
p[i]=(bin[0]*2)-bin[(ll)p[i]];
ans+=(ld)rd()*p[i]; p[i]+=p[i-1];
}
for(R ll i=1,l,r,w;i<=m;++i){
l=rd(); r=rd(); w=rd();
ans+=(ld)w*p[r]-(ld)w*p[l-1];
printf("%lld\n",(ll)floor(ans+eps));
}
return 0;
}