2020牛客暑期多校训练营(第九场)C Groundhog and Gaming Time 题解
题意:
在0~1e9 上一共有n个区间,每个区间有0.5的概率出现,问出现的区间的并集的长度的平方期望为多少。
由于是长度的平方,无法直接将区间长度相加,所以,我们考虑化式子让问题变得简单。
显然:
(a+b+c+d+e)^2=a*(a+b+c+d+e)+b*(a+b+c+d+e)+c*(a+b+c+d+e)+d*(a+b+c+d+e)+e*(a+b+c+d+e)
(a+b+c)^2+(c+d+e)^2=…… c*(a+b+c)+c*(c+d+e) ……
=…… c*((a+b+c)+(c+d+e)) ……
我们可以借助这个式子求出来每一个由n个区间的左右端点划分成的2n-1个小区间对于答案的贡献。
所以对于一个小区间,它的贡献是 (长度)*(所有包含它的大区间并集的长度和)
小区间的长度很好求,难得是求所有包含它的大区间并集的长度和。
我们不妨通过求各个小区间的贡献求出这些区间的并集的长度和。
每个小区间的贡献为(2^cnt-1)*(小区间长度),我们发现-1非常烦人。
但是我们可以通过化式子发现所有的-1项的和恰好为整个区间的长度的平方,可以在之后一次结清。
这样,每个小区间的贡献就为(2^cnt)*(区间长度)
这就非常好求了,我们可以通过线段树来进行维护。
现在让我们整理一下思路:
首先,我们拆分出一个个小区间,然后对于从左到右枚举这些小区间。
当我们枚举到一个小区间的时候,我们用线段树去求出所有包含它的大区间并集的长度和。
之后,我们减去区间长度的平方并除以2^n。得到答案
具体实现细节请看代码。
1 #include<iostream> 2 #include<cstdlib> 3 #include<cstdio> 4 #include<cstring> 5 #include<cmath> 6 #include<algorithm> 7 #include<map> 8 #define N 500005 9 using namespace std; 10 int n; 11 struct ro{ 12 int l,r; 13 }A[N]; 14 const int p=998244353; 15 int inv; 16 int B[N],C[N]; 17 bool cmpl(int x,int y) 18 { 19 return A[x].l<A[y].l; 20 } 21 bool cmpr(int x,int y) 22 { 23 return A[x].r<A[y].r; 24 } 25 map<int,int>ma; 26 int zz,D[2*N]; 27 struct no{ 28 int left,right,mid; 29 long long sum,tmp; 30 }node[N*8]; 31 void build(int left,int right,int x) 32 { 33 node[x].left=left,node[x].right=right; 34 node[x].tmp=1; //tmp表示这个区间被覆盖了log2(tmp)次 35 node[x].sum=D[right+1]-D[left]; //sum表示这个区间的贡献和 36 if(left==right) 37 { 38 return; 39 } 40 int mid=(left+right)>>1; 41 node[x].mid=mid; 42 build(left,mid,x<<1); 43 build(mid+1,right,x<<1|1); 44 } 45 long long ksm(long long x,long long z) 46 { 47 long long ans=1; 48 while(z) 49 { 50 if(z&1) 51 { 52 ans=ans*x%p; 53 } 54 x=x*x%p; 55 z>>=1; 56 } 57 return ans; 58 } 59 void change(int left,int right,int x,int da) 60 { 61 if(node[x].left==left&&node[x].right==right) 62 { 63 node[x].sum=node[x].sum*da%p; 64 node[x].tmp=node[x].tmp*da%p; 65 return ; 66 } 67 int mid=node[x].mid; 68 if(left>mid) change(left,right,x<<1|1,da); 69 else if(right<=mid) change(left,right,x<<1,da); 70 else change(left,mid,x<<1,da),change(mid+1,right,x<<1|1,da); 71 node[x].sum=(node[x<<1].sum+node[x<<1|1].sum)%p*node[x].tmp%p; 72 } 73 int main() 74 { 75 inv=ksm(2,p-2); 76 scanf("%d",&n); 77 for(int i=1;i<=n;i++) 78 { 79 scanf("%d%d",&A[i].l,&A[i].r); 80 A[i].r++; //方便计算让右端点+1 81 B[i]=C[i]=i; 82 if(!ma[A[i].l]) 83 { 84 zz++; 85 D[zz]=A[i].l; 86 ma[A[i].l]=1; 87 } 88 if(!ma[A[i].r]) 89 { 90 zz++; 91 D[zz]=A[i].r; 92 ma[A[i].r]=1; 93 } 94 } 95 if(!ma[0]) //在外面套上一个边界,方便计算 96 { 97 zz++; 98 D[zz]=0; 99 ma[0]=zz; 100 } 101 if(!ma[1e9+1]) 102 { 103 zz++; 104 D[zz]=1e9+1; 105 ma[1e9+1]=zz; 106 } 107 sort(B+1,B+n+1,cmpl); //将区间按照左端点排序 108 sort(C+1,C+n+1,cmpr); //将区间按照右端点排序 109 sort(D+1,D+zz+1); 110 for(int i=1;i<=zz;i++) 111 { 112 ma[D[i]]=i; 113 } 114 int li=1,ri=1; 115 build(1,zz-1,1); 116 long long ans=0; 117 for(int i=1;i<zz;i++) 118 { 119 for(;A[B[li]].l<=D[i]&&li<=n;li++) //计算新的对 [ D[i],D[i+1]) 有影响的大区间 的贡献 120 { 121 change(ma[A[B[li]].l],ma[A[B[li]].r]-1,1,2); 122 } 123 124 for(;A[C[ri]].r<=D[i]&&ri<=n;ri++) //消去新的不对[ D[i],D[i+1]) 有影响的大区间 的贡献 125 { 126 change(ma[A[C[ri]].l],ma[A[C[ri]].r]-1,1,inv); 127 } 128 ans=(ans+1ll*node[1].sum*(D[i+1]-D[i])%p)%p; 129 } 130 ans=(ans-1ll*(1000000001)*(1000000001)%p+p)%p; // 减去多算的那部分贡献 131 ans=ans*ksm(inv,n)%p; //除以2^n 132 printf("%lld\n",ans%p); 133 return 0; 134 }