题目描述
样例
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
1、E(A)=P(A)∗A 显然不行,如果所有的情况枚举显然超时了。
2
、
解
方
程
2、解方程
2、解方程 这题也没有想再来一瓶那样的可以列出简单的方程,否定。
3
、
期
望
d
p
3、期望dp
3、期望dp 这是官方的做法,比较繁琐,但是也可以做。
4
、
解
决
单
位
的
期
望
后
累
加
4、解决单位的期望后累加
4、解决单位的期望后累加 这是本篇博客介绍的做法,代码简单。
我们首先来考虑去掉平方之后怎么做。其实是比较简单的,我们不妨画个图简绍一下。
如果有①②③三个区间,我们可以将它们离散化在一条直线上,然后左右端点都分开,得到了
1
∼
5
1\sim 5
1∼5的小线段,然后我们对于每个区间都考虑一下:
比如1号线段,如果要取到1号,那么肯定要取区间①,并且②③都不能取,否则∩就不是1号了,因此其概率是
1
8
\frac{1}{8}
81,总共8种取法,一种满足,因此期望是
∣
1
区
间
∣
8
\frac{|1区间|}{8}
8∣1区间∣。
同样的,可以得到:
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
x2∗x2//取区间①②
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)=2n2a−1∗∣Q∣
显然,不能全部都不取,这里的式子都比较好推的,可以结合上面的图示理解一下。
那么对于所有的线段
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|)
=∣Pi∣∗∑j(2n2aj−1∗∣Qj∣)
=
∣
P
i
∣
∗
∑
j
(
2
a
j
2
n
∗
∣
Q
j
∣
)
−
不
取
的
期
望
\,\,\,\,\,\,\,\,\,\,=|P_i|*\sum_j(\frac{2^{a_j}}{2^n}*|Q_j|)-不取的期望
=∣Pi∣∗∑j(2n2aj∗∣Qj∣)−不取的期望
那么中间的那段可以用线段树来维护,然后最后再减去不取的。
更多的细节看代码。
代码
#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
这段代码有其他的一些注释,可以参考。
这题的思路真的比较厉害,这属于套娃的思想,在原来没有平方的基础上,把平方又拆成同样的问题,太强了。