2020牛客暑期多校训练营Groundhog and Gaming Time(数学期望,线段树,逆元)

Groundhog and Gaming Time

题目描述

在这里插入图片描述

样例

input:
6
2 2
1 2
1 4
1 5
3 5
3 6
output:
405536771

题目大意

给定 n n n个区间 ( L i , R i ) (L_i,R_i) (Li,Ri),每个区间有 1 2 \frac{1}{2} 21的概率取到,假设取了 m m m个,求:
E ( ∣ ( L 1 , R 1 ) ∩ ( L 2 , R 2 ) ∩ ( L 3 , R 3 ) . . . ∩ ( L m , R m ) ∣ 2 ) E(|(L_1,R_1)\cap(L_2,R_2)\cap(L_3,R_3)...\cap(L_m,R_m)|^2) E((L1,R1)(L2,R2)(L3,R3)...(Lm,Rm)2)
其中 E ( A ) E(A) E(A)表示 A A A的期望。

分析

首先我们看到期望先回顾期望的几种解法:
1 、 E ( A ) = P ( A ) ∗ A 1、E(A)=P(A)*A 1E(A)=P(A)A 显然不行,如果所有的情况枚举显然超时了。
2 、 解 方 程 2、解方程 2 这题也没有想再来一瓶那样的可以列出简单的方程,否定。
3 、 期 望 d p 3、期望dp 3dp 这是官方的做法,比较繁琐,但是也可以做。
4 、 解 决 单 位 的 期 望 后 累 加 4、解决单位的期望后累加 4 这是本篇博客介绍的做法,代码简单。

我们首先来考虑去掉平方之后怎么做。其实是比较简单的,我们不妨画个图简绍一下。
在这里插入图片描述
如果有①②③三个区间,我们可以将它们离散化在一条直线上,然后左右端点都分开,得到了 1 ∼ 5 1\sim 5 15的小线段,然后我们对于每个区间都考虑一下:
比如1号线段,如果要取到1号,那么肯定要取区间①,并且②③都不能取,否则∩就不是1号了,因此其概率是 1 8 \frac{1}{8} 81,总共8种取法,一种满足,因此期望是 ∣ 1 区 间 ∣ 8 \frac{|1区间|}{8} 81
同样的,可以得到:
2号,概率是取①,取②或者取①和②,为 3 8 \frac{3}{8} 83,期望就乘上长度就可以了……

那么对于一段线段,我们可以统计有多少区间是覆盖它的,然后算一下再除以所有的情况就可以了。

以上是去掉平方之后的,但是题目中是有平方的,因此,我们需要将平方搞回去。那么我们假设对于区间 i i i的期望是 x i x_i xi,则有:
E = ( x 1 + x 2 + . . . + x n ) 2 E=(x_1+x_2+...+x_n)^2 E=(x1+x2+...+xn)2
       = x 1 ( x 1 + x 2 + . . . + x n ) + x 2 ( x 1 + x 2 + . . . + x n ) + . . . + x n ( x 1 + x 2 + . . . + x n ) \,\,\,\,\,\,=x_1(x_1+x_2+...+x_n)+x_2(x_1+x_2+...+x_n)+...+x_n(x_1+x_2+...+x_n) =x1(x1+x2+...+xn)+x2(x1+x2+...+xn)+...+xn(x1+x2+...+xn)
可以发现,对于一个 x i x_i xi,可以直接在平方中给它拆开来。
具体一点,就拿上面的栗子:
我们考虑 x 2 x_2 x2,那么有这些式子:
E = ( x 1 + x 2 ) 2 + x 2 2 + ( x 2 + x 3 + x 4 ) 2 E=(x_1+x_2)^2+x_2^2+(x_2+x_3+x_4)^2 E=(x1+x2)2+x22+(x2+x3+x4)2//即取区间①,区间①②,区间②的情况累加
这里我们考虑 x 2 x_2 x2对答案的贡献有以下:
x 2 ( x 1 + x 2 ) x_2(x_1+x_2) x2(x1+x2)//取区间①
x 2 ∗ x 2 x_2*x_2 x2x2//取区间①②
x 2 ( x 2 + x 3 + x 4 ) x_2(x_2+x_3+x_4) x2(x2+x3+x4)//取区间②
那么经过上面去掉平方的经验, x 2 x_2 x2应该是很好求的,重点是解决后面一坨东西怎么算。

同样的,我们考虑这些后面的一段在给定的区间中出现了几次。
假设我当前枚举到线段 Q Q Q,并且它出现在区间中 a a a次,则有:
E ( Q ) = 2 a − 1 2 n ∗ ∣ Q ∣ E(Q)=\frac{2^a-1}{2^n}*|Q| E(Q)=2n2a1Q
显然,不能全部都不取,这里的式子都比较好推的,可以结合上面的图示理解一下。
那么对于所有的线段 P i P_i Pi贡献为:
贡献 = ∑ E ( P i ) =\sum E(P_i) =E(Pi)
           = ∣ P i ∣ ∗ ∑ j ( 2 a j − 1 2 n ∗ ∣ Q j ∣ ) \,\,\,\,\,\,\,\,\,\,=|P_i|*\sum_j(\frac{2^{a_j}-1}{2^n}*|Q_j|) =Pij(2n2aj1Qj)
           = ∣ P i ∣ ∗ ∑ j ( 2 a j 2 n ∗ ∣ Q j ∣ ) − 不 取 的 期 望 \,\,\,\,\,\,\,\,\,\,=|P_i|*\sum_j(\frac{2^{a_j}}{2^n}*|Q_j|)-不取的期望 =Pij(2n2ajQj)
那么中间的那段可以用线段树来维护,然后最后再减去不取的。
更多的细节看代码。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MAX=1e9+1;
const int MAXN=1e6+10;
const int MOD=998244353;
ll inv,p[MAXN],l[MAXN],r[MAXN],idl[MAXN],idr[MAXN];
ll ksm(ll a,ll p){ll ret=1;while(p){if(p&1) ret=ret*a%MOD;a=a*a%MOD,p>>=1;}return ret;}
struct tree{
	int l,r;
	ll sum,inc;
}tr[MAXN<<2];
void build(int i,int l,int r){
	tr[i].l=l;tr[i].r=r;
	tr[i].sum=p[r+1]-p[l]%MOD;tr[i].inc=1;
	if(l==r) return;int mid=l+r>>1;
	build(i<<1,l,mid);build(i<<1|1,mid+1,r);
}
void update(int i,int l,int r,ll v){
	if(tr[i].l>=l&&tr[i].r<=r){
		tr[i].sum=tr[i].sum*v%MOD;
		tr[i].inc=tr[i].inc*v%MOD;return;
	}int mid=tr[i].l+tr[i].r>>1;
	if(r<=mid) update(i<<1,l,r,v);
	else if(l>mid) update(i<<1|1,l,r,v);
	else update(i<<1,l,mid,v),update(i<<1|1,mid+1,r,v);
	tr[i].sum=(tr[i<<1].sum+tr[i<<1|1].sum)%MOD*tr[i].inc%MOD;
}//线段树区间乘
bool cmpl(int x,int y){return l[x]<l[y];}
bool cmpr(int x,int y){return r[x]<r[y];}
int main()
{
	inv=ksm(2,MOD-2);//因为有除法所以用逆元
	int n,m=2;
	scanf("%d",&n);p[1]=0;p[2]=MAX;//防止错误,将线段的左右端点赋值为0和inf
	for(int i=1;i<=n;i++){
		scanf("%d%d",&l[i],&r[i]);
		p[++m]=l[i];p[++m]=++r[i];//存入所有区间的左右端点,这样可以像上面图示一样分开
	}sort(p+1,p+1+m);
	m=unique(p+1,p+1+m)-p-1;//离散化
	build(1,1,m-1);//线段树存储的是线段的长度,初始的时候都是1
	for(int i=1;i<=n;i++){
		l[i]=lower_bound(p+1,p+1+m,l[i])-p;
		r[i]=lower_bound(p+1,p+1+m,r[i])-p;
		idl[i]=idr[i]=i;//这是记录从左边数第i个左右端点所属的区间的编号
	}sort(idl+1,idl+1+n,cmpl);sort(idr+1,idr+1+n,cmpr);//然后按照这个排序
	int li=1,ri=1;ll ans=0;
	for(int i=1;i<m;i++){
	//遍历区间[i,i+1],维护包含该区间的线段的交集的期望。这个期望通过计算所有区间的贡献和得到 
	//从m个线段,找出a个线段包含区间[i,i+1]。计算这a个线段的交集的期望,等于每个区间的贡献总和。
	//a个线段,有b个线段覆盖区间[j,j+1],则区间[j,j+1]的贡献=长度*(2^b-1)/2^n。其中-1和/2^n最后再去除 
		for(li;l[idl[li]]<=i&&li<=n;li++)
			update(1,l[idl[li]],r[idl[li]]-1,2);
		//乘上所有在这个区间内的线段贡献
		for(ri;r[idr[ri]]<=i&&ri<=n;ri++)
			update(1,l[idr[ri]],r[idr[ri]]-1,inv);
		//除去所有不在这个区间内的,用inv代替除
		ans=(ans+tr[1].sum*(p[i+1]-p[i])%MOD)%MOD;//然后累加贡献
	}ans=(ans-MAX%MOD*MAX%MOD+MOD)%MOD;//这里是去除最后都不取的情况,即处理上面说的-1
	ans=(ans*ksm(inv,n))%MOD;//这里是/2^n
	printf("%lld\n",ans%MOD);
}

END

这段代码有其他的一些注释,可以参考。

这题的思路真的比较厉害,这属于套娃的思想,在原来没有平方的基础上,把平方又拆成同样的问题,太强了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值