Omeed 线段树

目录

  • 题面
  • 题解
  • 代码

题面






2.12 - - -

题解

大概还是挺妙的?
首先基础分和连击分互不干扰,所以可以分开统计。
基础分的统计比较简单,等于:
\[A \sum_{i = l}^{r} p_i\]
连击分的统计就比较复杂了,因为是求期望,根据期望的线性性,我们可以先算出\(f_i\)表示每个音符的期望连击分,再计算整个区间的期望连击分。
观察连击分的统计方法,可以知道,区间其实是互不干扰的,也就是说,每个区间中的期望连击分,其实都是在对进入这个区间时的期望连击分\(f_{l - 1}\)的一个叠加和增幅。
考虑区间的期望连击分可以表示为:
\[B \sum_{i = l}^r p_i (f_{i - 1} + 1)\]
因为只有这次打到了完美才可以计入这个音符的贡献,所以这次的贡献是建立在当前音符完美的情况下的,所以贡献就是\(p_i(f_{i - 1} + 1)\)了。
考虑\(f_i\)如何转移。
\[f_i = p_i(f_{i - 1} + 1) + (1 - p_i)f_{i - 1}t\]
\[ = (p_i + t(1 - p_i))f_{i - 1} + p_i\]
观察到这是一个类似于\(kx + b\)的形式,因此对于一个\(f_i\),如果一个\(j\)满足\(j \le i\),那么一定可以表示为\(f_i = kf_j + b\)的形式。
那么对于区间\([l, r]\),因为其中每个\(f_i\),都可以表示为类似\(kf_{l - 1} + b\)的形式,因此,这个区间的连击分也一定可以表示为\(kf_{l - 1} + b\)的形式。
因此我们考虑线段树,对于区间\([l, r]\)我们维护5个变量,\(k, b, sumb, sumk, sump\),其中\(sump\)是用来算基础分的,\(sumb, sumk\)就是区间连击分的系数,\(k, b\)则是\(f_r = kf_{l - 1} + b\)中的\(k\)和\(b\).
因为\(B\)是对于整个区间的系数,因此我们可以先不考虑它,直接统计后面的部分,最后再乘上\(B\)即可。
因此我们考虑如何合并2个区间\([l, mid], [mid + 1, r]\).
根据前面的推导,现在有
\[f_{mid} = k_l f_{l - 1} + b_l, \quad f_{r} = k_r f_{mid} + b_r\]
现在要合并这2个变量,我们只需要把后者表示为\(kf_{l - 1} + b\)的形式即可。
所以直接把\(f_{mid}\)带入后面的等式化简就行了,化简出来新变量的\(k = k_l k_r, \quad b = k_rb_l + b_r\)
然后来考虑合并区间信息:
现在我们有:
\[sumk_l f_{l - 1} + sumb_l\]
\[sumk_r f_{mid} + sumb_r\]
我们现在要得到的新区间应该要形如第一个区间的样子,因为第一个区间已经是这样了,所以我们只需要转化一下第二个区间,然后和第一个区间加在一起就行了。
我们直接带入上面的\(f_{mid} = k_l f_{l - 1} + b_l\),然后化简并和第一个区间的式子加在一起,最后得到新的\(sumk = sumk_r k_l + sumk_l, \quad sumb = sumk_r b_l + sumb_r + sumb_l\)
最后
\[ans[l][r] = B \cdot sumb[l][r] + A \cdot sump[l][r]\]

代码

#include<bits/stdc++.h>
using namespace std;
#define R register int
#define LL long long
#define AC 501000
#define ac 2001000
#define p 998244353
#define mo(x) ((x) % p)
#define mul(a, b) (1LL * (a) * (b) % p)//error !!!都要用(a), (b)...啊
#define h(x, y) (mul((x), qpow((y), p - 2)))

int n, m, t, A, B;
int pi[AC];

struct node{
    int sumk, sumb, k, b, sump;
}tree[ac];

inline int read()
{
    int x = 0;char c = getchar();
    while(c > '9' || c < '0') c = getchar();
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x;
}

inline void up(int &a, int b) {a += b; if(a < 0) a += p; if(a >= p) a -= p;}
inline int ad(int a, int b) {a += b; if(a < 0) a += p; if(a >= p) a -= p; return a;}

inline int qpow(int x, int have)
{
    int rnt = 1;
    while(have)
    {
        if(have & 1) rnt = mul(rnt, x);
        x = mul(x, x), have >>= 1;
    }
    return rnt;
}

void pre()
{
    n = read();//对于正解来说没有什么用的输入
    n = read(), m = read();
    int a = read(), b = read();
    t = h(a, b), A = read(), B = read();
    for(R i = 1; i <= n; i ++) a = read(), b = read(), pi[i] = h(a, b);

}

node merge(node ll, node rr)
{
    node x;
    x.k = mul(ll.k, rr.k), x.b = ad(mul(rr.k, ll.b), rr.b);
    x.sumk = ad(mul(rr.sumk, ll.k), ll.sumk);
    x.sumb = ad(mul(rr.sumk, ll.b), ad(rr.sumb, ll.sumb));
    x.sump = ad(ll.sump, rr.sump);
    return x;
}

#define update(x) tree[x] = merge(tree[x << 1], tree[(x << 1) + 1]);

node make(int now)
{
    node x;
    x.k = ad(pi[now], mul(t, 1 - pi[now]));
    x.b = x.sumk = x.sumb = x.sump = pi[now];
    return x;
}

void build(int x, int ll, int rr)
{
    if(ll == rr) {tree[x] = make(ll); return ;}
    int mid = (ll + rr) >> 1;
    build(x << 1, ll, mid), build((x << 1) + 1, mid + 1, rr);
    update(x);
}

void change(int x, int l, int r, int w)
{
    if(l == r) {tree[x] = make(w); return ;}
    int mid = (l + r) >> 1;
    if(w <= mid) change(x << 1, l, mid, w);
    else change((x << 1) + 1, mid + 1, r, w);
    update(x);
}

node find(int x, int l, int r, int ll, int rr)
{
    if(l == ll && r == rr) return tree[x];
    int mid = (l + r) >> 1;
    if(rr <= mid) return find(x << 1, l, mid, ll, rr);
    else if(ll > mid) return find((x << 1) + 1, mid + 1, r, ll, rr);
    else
    {
        node a = find(x << 1, l, mid, ll, mid);
        node b = find((x << 1) + 1, mid + 1, r, mid + 1, rr);
        return merge(a, b);
    }
}

void work()
{
    for(R i = 1; i <= m; i ++)
    {
        int o = read();
        if(!o)
        {
            int x = read(), a = read(), b = read();
            pi[x] = h(a, b), change(1, 1, n, x);
        }
        else
        {
            int ll = read(), rr = read();
            node x = find(1, 1, n, ll, rr);
            //int ans = mul(ad(mul(x.sumk, pi[ll]), ad(x.sumb, pi[ll])), B);
            int ans = mul(x.sumb, B);
            up(ans, mul(A, x.sump));
            printf("%d\n", ans);
        }
    }
}

int main()
{
    freopen("in.in", "r", stdin);
    pre();
    build(1, 1, n);
    work();
    fclose(stdin);
    return 0;
}

原文地址:https://www.cnblogs.com/ww3113306/p/10366975.html

时间: 2024-11-08 20:55:20

Omeed 线段树的相关文章

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ4942】[Noi2017]整数 线段树+DFS(卡过)

[BZOJ4942][Noi2017]整数 题目描述去uoj 题解:如果只有加法,那么直接暴力即可...(因为1的数量最多nlogn个) 先考虑加法,比较显然的做法就是将A二进制分解成log位,然后依次更新这log位,如果最高位依然有进位,那么找到最高位后面的第一个0,将中间的所有1变成0,那个0变成1.这个显然要用到线段树,但是复杂度是nlog2n的,肯定过不去. 于是我在考场上yy了一下,这log位是连续的,我们每次都要花费log的时间去修改一个岂不是很浪费?我们可以先在线段树上找到这段区间

bzoj1798: [Ahoi2009]Seq 维护序列seq 线段树

题目传送门 这道题就是线段树 先传乘法标记再传加法 #include<cstdio> #include<cstring> #include<algorithm> #define LL long long using namespace std; const int M=400010; LL read(){ LL ans=0,f=1,c=getchar(); while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}

Vijos P1066 弱弱的战壕【多解,线段树,暴力,树状数组】

弱弱的战壕 描述 永恒和mx正在玩一个即时战略游戏,名字嘛~~~~~~恕本人记性不好,忘了-_-b. mx在他的基地附近建立了n个战壕,每个战壕都是一个独立的作战单位,射程可以达到无限(“mx不赢定了?!?”永恒[email protected][email protected]). 但是,战壕有一个弱点,就是只能攻击它的左下方,说白了就是横纵坐标都不大于它的点(mx:“我的战壕为什么这么菜”ToT).这样,永恒就可以从别的地方进攻摧毁战壕,从而消灭mx的部队. 战壕都有一个保护范围,同它的攻击

luogu 1712 区间(线段树+尺取法)

题意:给出n个区间,求选择一些区间,使得一个点被覆盖的次数超过m次,最小的花费.花费指的是选择的区间中最大长度减去最小长度. 坐标值这么大,n比较小,显然需要离散化,需要一个技巧,把区间转化为半开半闭区间,然后线段树的每一个节点表示一个半开半闭区间. 接着我们注意到需要求最小的花费,且这个花费只与选择的区间集合中的最大长度和最小长度有关. 这意味着如果最大长度和最小长度一定,我们显然是需要把中间长度的区间尽量的选择进去使答案不会变的更劣. 不妨把区间按长度排序,枚举每个最小长度区间,然后最大区间

【BZOJ】1382: [Baltic2001]Mars Maps (线段树+扫描线)

1382: [Baltic2001]Mars Maps Time Limit: 5 Sec  Memory Limit: 64 MB Description 给出N个矩形,N<=10000.其坐标不超过10^9.求其面积并 Input 先给出一个数字N,代表有N个矩形. 接下来N行,每行四个数,代表矩形的坐标. Output 输出面积并 Sample Input 2 10 10 20 20 15 15 25 30 Sample Output 225 本以为是傻逼题,没想到不容易啊- 线段树+扫描

BZOJ 1012: [JSOI2008]最大数maxnumber(线段树)

012: [JSOI2008]最大数maxnumber Time Limit: 3 Sec  Memory Limit: 162 MB Description 现在请求你维护一个数列,要求提供以下两种操作:1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度.2. 插入操作.语法:A n 功能:将n加上t,其中t是最近一次查询操作的答案(如果还未执行过查询操作,则t=0),并将所得结果对一个固定的常数D取模,将所得答案插入到数列

HDU 1754 I Hate It(线段树之单点更新,区间最值)

I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 70863    Accepted Submission(s): 27424 Problem Description 很多学校流行一种比较的习惯.老师们很喜欢询问,从某某到某某当中,分数最高的是多少. 这让很多学生很反感.不管你喜不喜欢,现在需要你做的是,就是按照老师的

线段树入门总结

线段树的入门级 总结   线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点.      对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b].因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度.      使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN).而未优化的空间复杂度为2N,因此有时需要离散化让空间