Groundhog and Gaming Time
题目描述;
在
晚上,
个同学包括
做出了一个了不起的决定:他们准备参加一个车队,他们将分担一夜直到黎明。
那天晚上,他们一个接一个地上网。第
个同学将从
上线。但是有人报告了一条消息:他们的老师晚上会来。每个人都有
的可能性来接收消息,而接收到该消息的人将不会在晚上上网。他们的可用游戏时间是所有游戏者都在线时。因此,他们开始思考一个问题:对他们的可用时间模的平方的期望是什么
简化的问题:有
个段
,每个段都有可能选择
,问题是计算
的期望值。
表示选定的第
个段的数量。
输入描述:
第一行包含一个整数
,表示学生人数。
接下来的
行分别包含两个整数
,这表示第
个学生的游戏时间。
输出描述:
输出一行包含答案模 。
样例输入:
6
2 2
1 2
1 4
1 5
3 5
3 6
样例输出:
405536771
思路:
求期望的方法有很多种,这题我们选择期望dp求期望的方法。
首先由于数据较大我们需要先对数据进行离散化,然后我们按左右端点升序排序并考虑每一段对答案的贡献。
我们设区间A出现在b条线段中,则有:
应为不能全选,所以要减一。
所以:
听说两个‘|’还可以表示size
那么在所有
条线段中
的贡献
条线段包含
:
我们可以用线段树来维护
,最后除以
并减去
即可
具体细节请看代码。
:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 998244353;
const int MAXN = 1e6+5;
const int N = 1e9 + 1;
ll a[MAXN << 2], b[MAXN << 2], trl[MAXN], trr[MAXN], l[MAXN];
ll r[MAXN], lsh[MAXN], cnt, ans;
int n;
ll ksm (ll a, ll b) {
ll ret = 1;
while (b){
if(b & 1) ret = ret * a % mod;
a = a * a % mod;
b >>= 1;
}
return ret;
}//快速幂求逆元
bool cmp1 (int x, int y){ return l[x] < l[y]; }
bool cmp2 (int x, int y){ return r[x] < r[y]; }
void build_tree (int pos, int l, int r){//建线段树
a[pos] = lsh[r + 1] - lsh[l];
b[pos] = 1;
if (l == r) return;
int mid = l + r >> 1;
build_tree (pos << 1, l, mid);
build_tree (pos << 1 | 1, mid + 1, r);
}
void f (int pos, int l, int r, ll num, int x, int y){//将区间[l,r]的权值都*num
if(l <= x && r >= y){
a[pos] = a[pos] * num % mod;
b[pos] = b[pos] * num % mod;
return;
}
int mid = x + y >> 1;
if (l <= mid) f (pos << 1, l, r, num, x, mid);
if (r > mid) f (pos <<1 | 1, l, r, num, mid + 1, y);
a[pos] = (a[pos << 1] + a[pos << 1 | 1]) * b[pos] % mod;//b[pos]就是lazy标记,这里可以不用下推标记。
}
int main (){
lsh[1] = 0;
lsh[2] = N;//添加最左最优的端点,用于处理所有线段都不选择的情况
cnt = 2;
scanf ("%d", &n);
for (int i = 1; i <= n; ++i){
scanf("%lld%lld", l + i ,r + i);
lsh[++cnt] = l[i];
lsh[++cnt] = ++r[i];
}
sort (lsh + 1, lsh + cnt + 1);
cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;//离散化
build_tree(1, 1, cnt - 1);//建线段树,i点的初始权值为区间[i,i+1]的长度
for (int i = 1; i <= n; ++i){
l[i] = lower_bound(lsh + 1, lsh + cnt + 1, l[i]) - lsh;//映射到原值
r[i] = lower_bound(lsh + 1, lsh + cnt + 1, r[i]) - lsh;
}
for (int i = 1; i <= n; ++i) trl[i] = trr[i] = i;
sort (trl + 1, trl + n + 1, cmp1);//线段按左端点排序
sort (trr + 1, trr + n + 1, cmp2);//线段按右端点排序
int t1 = 1, t2 = 1;
for (int i = 1; i < cnt; ++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 (; t1 <= n && l[trl[t1]] <= i; ++t1)//添加左端点小于等于i的线段的贡献
f(1, l[trl[t1]], r[trl[t1]] - 1, 2, 1, cnt - 1);//添加贡献,等于该区间的权值*2
for (;t2 <= n && r[trr[t2]] <= i; ++t2)//删除右端点小于等于i的线段的贡献
f(1, l[trr[t2]], r[trr[t2]] - 1, ksm(2, mod - 2), 1, cnt - 1);//添加贡献,等于该区间的权值-2
ans = (ans + a[1] * (lsh[i + 1] - lsh[i])) % mod;//计入答案,此时ans=sum(方案权值*方案数)
}//注意此时统计完时,未减去考虑所有线段都不选择的情况,即贡献=长度*(2^b-1)/2^n中的-1
ans = (ans + mod - 1ll * N * N % mod) % mod;//减去所有线段都不选择的情况
for (int i = 1; i <= n; ++i)
ans = ans * ksm(2, mod - 2) % mod;//除2^n,得到期望
printf("%lld\n", ans);
}