2020牛客暑期多校训练营(第九场)—— Groundhog and Gaming Time
输入
6
2 2
1 2
1 4
1 5
3 5
3 6
输出
405536771
备注
1⩽n⩽5×10^5,0⩽Li⩽Ri⩽10^9.
题目大意
题解
不同于官方题解通过树形dp的思路,我们的方法是通过累加每种情况对期望的贡献来求解。
线段的交取决于最大的左端点以及最小的右端点,同时维护两个东西比较困难。
当然,我们先添加上最左最优的端点,处理所有线段都不选择的情况;
再按照线段左端点从大到小排序,那么排序后的线段的交,就取决于最小的右端点,以及第一个被选择的线段的左端点。离散化后构建好一棵线段树,映射到原值。ans=∑方案权值∗方案数
AC Code
#include<bits/stdc++.h>
#define ll long long
#define rep(i,x,y) for(ll i=(x);i<=(y);++i)
#define fr first
#define sc second
using namespace std;
const int N=1e6+10;
const int mx=1e9+1;
const int mo=998244353;
const int n2=(mo+1)/2;//2的逆元
ll a[N*4],b[N*4],p[N];int l[N],r[N],tl[N],tr[N];
void bd(int x,int l,int r)
{
a[x]=p[r+1]-p[l];
b[x]=1;
if(l==r)return;
int m=(l+r)>>1;
bd(x*2,l,m);
bd(x*2+1,m+1,r);
}//构建线段树
bool cmpl(int x,int y){return l[x]<l[y];}
bool cmpr(int x,int y){return r[x]<r[y];}
void f(int x,int l,int r,ll y,int L,int R)
{
if(l<=L&&r>=R)
{
a[x]=a[x]*y%mo;
b[x]=b[x]*y%mo;
return;
}
int m=(L+R)>>1;
if(l<=m)f(x*2,l,r,y,L,m);
if(r>m)f(x*2+1,l,r,y,m+1,R);
a[x]=(a[x*2]+a[x*2+1])*b[x]%mo;
}//将区间[l,r]的权值都*y
int main()
{
int n;
scanf("%d",&n);
int m=2;p[1]=0;p[2]=mx;//添加最左最优的端点
rep(i,1,n)
{
scanf("%d%d",&l[i],&r[i]);
p[++m]=l[i];
p[++m]=++r[i];
}
sort(p+1,p+m+1);
m=unique(p+1,p+m+1)-p-1;//离散化
bd(1,1,m-1);//构建线段树
rep(i,1,n)
{
l[i]=lower_bound(p+1,p+m+1,l[i])-p;
r[i]=lower_bound(p+1,p+m+1,r[i])-p;
}
rep(i,1,n)
{
tl[i]=i;
tr[i]=i;
}
sort(tl+1,tl+n+1,cmpl);
sort(tr+1,tr+n+1,cmpr);//线段按排左右端点排序
int t1=1,t2=1;
ll ans=0;
rep(i,1,m-1)
{//遍历区间[i,i+1]
for(;t1<=n&&l[tl[t1]]<=i;++t1)f(1,l[tl[t1]],r[tl[t1]]-1,2,1,m-1);//添加左端点小于等于i的线段的贡献
for(;t2<=n&&r[tr[t2]]<=i;++t2)f(1,l[tr[t2]],r[tr[t2]]-1,n2,1,m-1);//删除右端点小于等于i的线段的贡献
ans=(ans+a[1]*(p[i+1]-p[i]))%mo;
}
ans=(ans+mo-1ll*mx*mx%mo)%mo;//减去所有线段都不选择的情况
rep(i,1,n)ans=ans*n2%mo;//除2^n,得到期望
printf("%lld\n",ans);
}