P2221 高速公路 线段树 + 期望

题目大意:

题目链接
给出一个序列 A [ ] A[ ] ,初始值为0。
有一个问题:
给定 l r l,r ,在第 l l 个到第 r r 个数之间等概率随机的挑选两个不同的数 a , b a, b , 那么 a , b a,b 之间的区间和的期望值是多少。
有以下两种操作:
C l r v 表示区间从 l l r r 的值增加 v v
Q l r 回答上述问题。

思路:

题目中的 l , r l,r 表示节点,在这里我们直接用 l , r l,r 表示节点之间的路,那么我们进行操作之间要把 r --r ,才能让节点对应成节点之间的路,具体细节参看主函数。

容易想到暴力算期望的方法,每个区间被取到的概率为: 1 C r l + 1 2 \frac{1}{C_{r - l + 1}^{2}}
那么期望就是 : 1 C r l + 1 2 i = l r j = l r ( i , j ) \frac{1}{C_{r - l + 1}^{2}} * \sum_{i = l} ^ {r}\sum_{j = l}^{r} (i,j区间和)
显然这个式子不太容易计算。
我们考虑每个区间对期望的贡献: ( A [ a ] + A [ a + 1 ] + . . . + A [ b ] ) 1 C r l + 2 2 (A[a] + A[a + 1] + ...+A[b]) * \frac{1}{C_{r - l + 2}^{2}}

我们发现只要 A [ i ] A[i] 这个元素处于一个区间中,它就会对期望贡献 A [ i ] 1 C r l + 2 2 A[i] * \frac{1}{C_{r - l + 2}^{2}}
想到这儿,我们就可以从每一个元素来考虑这个问题,我们只需要计算出多少个区间包含 A [ i ] A[i] , 然后包含 A [ i ] A[i] 的区间个数乘 A [ i ] 1 C r l + 2 2 A[i] * \frac{1}{C_{r - l + 2}^{2}} ,即是 A [ i ] A[i] 对期望的贡献值。我们一次考虑区间内的每一个元素即可。

根据组合数学的知识,我们可以知道,包含 A [ i ] A[i] 的区间个数为
( i l + 1 ) ( r i + 1 ) (i - l + 1) * (r - i + 1)

那么我们要求的最后的答案就是:
( i = l r ( i l + 1 ) ( r i + 1 ) A [ i ] ) 1 C r l + 2 2 (\sum_{i = l} ^ {r} (i - l + 1) * (r - i + 1) * A[i] ) * \frac{1}{C_{r - l + 2}^{2}}

那么接下来的问题就是上面式子的左半部分怎么求的问题:
我们将累加符号中的式子展开变成:
( 1 + r l l r + i ( l + r ) i 2 ) A [ i ] (1 + r - l - l * r + i * (l + r) - i^2) * A[i]
我们只需要在线段树中维护三个量: A [ i ] A[i] 的区间和, i A [ i ] i*A[i] 的区间和, i 2 A [ i ] i^2 * A[i] 的区间和即可通过线性的组合得到上面的式子。

还需要注意的一点是区间修改的操作,对于维护的第一个值来说就是普通的修改。
而对于第二个值来说,区间增加的值是 a d d ( l + l + 1 + l + 2 + . . . + r ) add*(l + l+ 1 + l + 2 +...+r) ,乘的是一个等差数列,也就是 a d d ( l + r ) ( r l + 1 ) / 2 add*(l + r) * (r - l + 1) / 2
对于第三个值来说,区间增加的值是 a d d ( l 2 + ( l + 1 ) 2 + . . . + r 2 ) add*(l^2+(l + 1)^2 + ...+r^2) ,对于右半部分我们预处理出 i 2 i^2 的前缀和,那么上式就变成 a d d ( p r e [ r ] p r e [ l 1 ] ) add*(pre[r]-pre[l - 1])
线段树部分完成,具体的细节可以看代码。

代码:

/**
* Author : Xiuchen
* Date : 2020-02-29-18.40.16
* Description : 高速公路
*/
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<cmath>
#include<math.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int maxn = 1e5 + 100;
ll gcd(ll a, ll b){
    return b ? gcd(b, a % b) : a;
}
struct SegmentTree{
    ll sum[5];
    ll add;
    int l, r;
    #define l(x) t[x].l
    #define r(x) t[x].r
    #define add(x) t[x].add
    #define sum1(x) t[x].sum[1]
    #define sum2(x) t[x].sum[2]
    #define sum3(x) t[x].sum[3]
}t[maxn * 4];
int n, m;
ll pre[maxn];
struct node{
    ll sum1, sum2, sum3;
};
void push_up(int p){
    sum1(p) = sum1(p * 2) + sum1(p * 2 + 1);
    sum2(p) = sum2(p * 2) + sum2(p * 2 + 1);
    sum3(p) = sum3(p * 2) + sum3(p * 2 + 1);
}
void push_down(int p){
    if(add(p)){
        sum1(p * 2) += add(p) * (r(p * 2) - l(p * 2) + 1);
        sum1(p * 2 + 1) += add(p) * (r(p * 2 + 1) - l(p * 2 + 1) + 1);
        sum2(p * 2) += add(p) * (l(p * 2) + r(p * 2)) * (r(p * 2) - l(p * 2) + 1) / 2;
        sum2(p * 2 + 1) += add(p) * (l(p * 2 + 1) + r(p * 2 + 1)) * (r(p * 2 + 1) - l(p * 2 + 1) + 1) / 2;
        sum3(p * 2) += add(p) * (pre[r(p * 2)] - pre[l(p * 2) - 1]);
        sum3(p * 2 + 1) += add(p) * (pre[r(p * 2 + 1)] - pre[l(p * 2 + 1) - 1]);
        add(p * 2) += add(p), add(p * 2 + 1) += add(p);
        add(p) = 0;
    }
}
void build(int p, int l, int r){
    l(p) = l, r(p) = r;
    if(l == r){
        sum1(p) = sum2(p) = sum3(p) = add(p) = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(p * 2, l, mid);
    build(p * 2 + 1, mid + 1, r);
    push_up(p);
}
void change(int p, int l, int r, ll d){
    if(l <= l(p) && r(p) <= r){
        add(p) += d;
        sum1(p) += d * (r(p) - l(p) + 1);
        sum2(p) += d * (l(p) + r(p)) * (r(p) - l(p) + 1) / 2;
        sum3(p) += d * (pre[r(p)] - pre[l(p) - 1]);
        return;
    }
    int mid = (l(p) + r(p)) >> 1;
    push_down(p);
    if(l <= mid) change(p * 2, l, r, d);
    if(mid < r) change(p * 2 + 1, l, r, d);
    push_up(p);
}
node query(int p, int l, int r){
    if(l <= l(p) && r(p) <= r){
        node ans = {sum1(p), sum2(p), sum3(p)};
        return ans;
    }
    int mid = (l(p) + r(p)) >> 1;
    push_down(p);
    if(l > mid) return query(p * 2 + 1, l, r);
    else if(r <= mid) return query(p * 2, l, r);
    else{
        node ans1 = query(p * 2, l, r);
        node ans2 = query(p * 2 + 1, l, r);
        node ans;
        ans.sum1 = ans1.sum1 + ans2.sum1, ans.sum2 = ans1.sum2 + ans2.sum2, ans.sum3 = ans1.sum3 + ans2.sum3;
        return ans;
    }
}
int main(){
    scanf("%d%d", &n, &m);
    build(1, 1, n);
    for(int i = 1; i <= n; i++) pre[i] = pre[i - 1] + 1LL * i * i;
    //预处理平方和,用与维护第三个值。
    while(m--){
        char op[5];
        ll l, r;
        scanf("%s%lld%lld", op, &l, &r);
        if(op[0] == 'C'){
            ll v;
            scanf("%lld", &v);
            change(1, l, r - 1, v);
            //更新第l个点到第r个点之间的路,也就是更新第l段路到第r-1段路。
        }
        else{
            r--;//r--,对应到路。
            node ans = query(1, l, r);
            ll tmp1 = (l + r) * ans.sum2 + ans.sum1 * (r - l - l * r + 1) - ans.sum3;
            ll tmp2 = (r - l + 2) * (r - l + 1) / 2;
            //这里是C(r - l + 2, 2),因为是在点之间取两个不同的点。
            //点的总数是r + 1 - l + 1.
            ll G = gcd(tmp1, tmp2);
            printf("%lld/%lld\n", tmp1 / G, tmp2 / G);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44607936/article/details/104584561