Omeed 线段树
题面
2.12 - - -
题解
大概还是挺妙的?
首先基础分和连击分互不干扰,所以可以分开统计。
基础分的统计比较简单,等于:
连击分的统计就比较复杂了,因为是求期望,根据期望的线性性,我们可以先算出fi表示每个音符的期望连击分,再计算整个区间的期望连击分。
观察连击分的统计方法,可以知道,区间其实是互不干扰的,也就是说,每个区间中的期望连击分,其实都是在对进入这个区间时的期望连击分fl−1的一个叠加和增幅。
考虑区间的期望连击分可以表示为:
因为只有这次打到了完美才可以计入这个音符的贡献,所以这次的贡献是建立在当前音符完美的情况下的,所以贡献就是pi(fi−1+1)了。
考虑fi如何转移。
观察到这是一个类似于kx+b的形式,因此对于一个fi,如果一个j满足j≤i,那么一定可以表示为fi=kfj+b的形式。
那么对于区间[l,r],因为其中每个fi,都可以表示为类似kfl−1+b的形式,因此,这个区间的连击分也一定可以表示为kfl−1+b的形式。
因此我们考虑线段树,对于区间[l,r]我们维护5个变量,k,b,sumb,sumk,sump,其中sump是用来算基础分的,sumb,sumk就是区间连击分的系数,k,b则是fr=kfl−1+b中的k和b.
因为B是对于整个区间的系数,因此我们可以先不考虑它,直接统计后面的部分,最后再乘上B即可。
因此我们考虑如何合并2个区间[l,mid],[mid+1,r].
根据前面的推导,现在有
现在要合并这2个变量,我们只需要把后者表示为kfl−1+b的形式即可。
所以直接把fmid带入后面的等式化简就行了,化简出来新变量的k=klkr,b=krbl+br
然后来考虑合并区间信息:
现在我们有:
我们现在要得到的新区间应该要形如第一个区间的样子,因为第一个区间已经是这样了,所以我们只需要转化一下第二个区间,然后和第一个区间加在一起就行了。
我们直接带入上面的fmid=klfl−1+bl,然后化简并和第一个区间的式子加在一起,最后得到新的sumk=sumkrkl+sumkl,sumb=sumkrbl+sumbr+sumbl
最后
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】Flutter适配HarmonyOS 5知识地图,实战解析+高频避坑指南
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合终身会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一则复杂 SQL 改写后有感
· golang中写个字符串遍历谁不会?且看我如何提升 50 倍
· C# 代码如何影响 CPU 缓存速度?
· 智能桌面机器人:使用 .NET 为树莓派开发 Wifi 配网功能
· C# 模式匹配全解:原理、用法与易错点
· 《程序员的底层思维》读后感
· 曾经风光无限的 Oracle DBA 已经落伍了吗?
· 一则复杂 SQL 改写后有感
· C# 锁机制全景与高效实践:从 Monitor 到 .NET 9 全新 Lock
· 接口被刷百万QPS,怎么防?