P2221 [HAOI2012]高速公路
区间修改区间询问,考虑线段树维护
对于一个询问 $l,r$ ,如果我们能求出总每种情况的总花费,那么最终答案就是总花费除 $C^2_{r-l+1}$
为了方便维护把每条边看成点,那么线段树有 $n-1$ 个叶子节点
考虑线段树两个相邻区间 $lc,rc$ 合并成区间 $O$
显然先设 $sum$ 表示当前区间的总费用,那么 $sum[O]$ 肯定先加上左右区间内部的贡献
接下来只要考虑跨过中间的贡献
考虑每条边单独产生的贡献,显然是边长乘经过次数
考虑一个 $lc$ 的一条边 $x$ 会被跨中间的路径经过几次,设边 $x$ 在区间 $O$ 中左边的点数为 $t[x]$(此处的点为原序列的点,不是线段树的点)
那么对于左边的每个点,它都可以与 $rc$ 的每个点作为一段路径,设 $rc$ 的包含的总边数为 $sz[r]$,那么总点数为 $sz[r]+1$,但是因为 $rc$ 最左边的点和 $lc$ 最右边的点是同一个,所以计算贡献的点只有 $sz[rc]$ 而不是 $sz[rc]+1$,之后有些地方也要注意到这点
所以贡献为 $t[x]*dis[x]*sz[rc]$,发现对于 $lc$ 的每个边 $x$ 它都要乘 $sz[rc]$ ,所以只要考虑维护所有 $t[x]*dis[x]$ 的和,设这个东西为 $suml[\ ]$,那么对 $sum[O]$的贡献就是 $suml[lc]*sz[rc]$
考虑合并时 $suml[O]$ 的变化,发现对于 $O$ 的左右子区间 $lc,rc$, $lc$ 对 $O$ 的贡献就是 $suml[lc]$,但是 $rc$ 对 $O$ 的贡献为 $suml[rc]$ 加上 $rc$ 的每条边新增加的贡献,其实就是 $rc$ 的所有 $t[\ ]$都增加了 $sz[lc]$ 总贡献增加 $tot[rc]*sz[lc]$ ( $tot[x]$ 为区间 $x$ 的每条边边长之和)
然后我们就可以维护 $suml$ 进而维护 $lc$ 对 $sum[o]$ 的贡献了,然后考虑 $rc$ 对 $sum[O]$ 的贡献
同样维护 $sumr[rc]$ 表示区间 $rc$ 所有边分别乘上它们右边的点数的和,同 $suml$ 差不多的方法维护
总结一下就是如下代码 (为了方便写进了结构体):
inline dat operator + (const dat &tmp) const { if(!sz) return tmp;//先不用管这个 dat res; res.clr(); res.sum= sum + tmp.sum + suml*tmp.sz + tmp.sumr*sz;//左右区间内部的贡献加上跨左右区间的贡献 res.suml= suml + tmp.suml + tmp.tot*sz;//维护suml res.sumr= tmp.sumr + sumr + tot*tmp.sz;//维护sumr res.tot=tot+tmp.tot; res.sz=sz+tmp.sz;//维护tot和sz return res; }
因为有区间修改,考虑标记 $add$ 对这些东西的影响:
首先 $tot$ 显然加上 $sz*add$
对于 $suml$,因为每条边都增加 $add$,而区间内 $t[x]$ 为等差数列 $1,2,3...sz[x]$,所以运用数列求和的方法求出总贡献增加量为:
$add+2add+3add+...+sz*add=sz(sz+1)/2*add$
$sumr$同理,
对于 $sum$ 就比较复杂,仍然考虑每条边增加的贡献,那么还是可以写出式子:
$(1*sz)add+(2*(sz-1))add+(3*(sz-2))add+...+(sz*1)add$
然后经过一波丧心病狂的化简:
$= ( sz+2(sz-1)+3(sz-2)+...+sz*(sz-(sz-1)) ) *add$
$= ( sz+2*sz+3*sz+...+sz*sz - (2(2-1)+3(3-1)+4(4-1)+...+sz(sz-1)) ) *add$
$= ( (1+2+3+...+sz)sz - ( 2^2+3^2+4^2+...+sz^2 - (2+3+4+...+sz) ) ) *add$
因为 $ \sum _{i=1}^{n}i^2=n(n+1)(2n+1)/6$
所以 $= ( (1+2+3+...+sz)sz - ( sz(sz+1)(2sz+1)/6 -1 - (2+3+4+...+sz) ) ) *add$
$= ( (1+2+3+...+sz)sz - ( sz(sz+1)(2sz+1)/6 - (1+2+3+4+...+sz) ) ) *add$
$= ( sz^2*(sz+1)/2 - ( sz(sz+1)(2sz+1)/6 - sz(sz+1)/2 ) ) *add$
$= ( (sz(sz+1)/2)*(sz+1) - sz(sz+1)(2sz+1)/6 ) *add$
$= ( sz(sz+1)^2/2 - sz(sz+1)(2sz+1)/6 ) *add$
所以最后增加的就是这个东西..........
那么更新节点代码如下:
inline void upd() { tot+=sz*add; suml+=sz*(sz+1)/2*add; sumr+=sz*(sz+1)/2*add; sum+=(sz*(sz+1)*(sz+1)/2-sz*(sz+1)*(sz*2+1)/6)*add; add=0; }
其他的就简单了,记得开 $unsigned\ long\ long$!
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> using namespace std; typedef unsigned long long ull; inline ull read() { ull x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const ull N=2e5+7; ull n,m; struct dat{ ull suml,sumr,sum,tot,sz,add; inline void clr() { suml=sumr=sum=tot=sz=add=0; } inline void upd() { tot+=sz*add; suml+=sz*(sz+1)/2*add; sumr+=sz*(sz+1)/2*add; sum+=(sz*(sz+1)*(sz+1)/2-sz*(sz+1)*(sz*2+1)/6)*add; add=0; } inline dat operator + (const dat &tmp) const { if(!sz) return tmp;//因为ans初始为空所以特判一下 dat res; res.clr(); res.sum= sum + tmp.sum + suml*tmp.sz + tmp.sumr*sz; res.suml= suml + tmp.suml + tmp.tot*sz; res.sumr= tmp.sumr + sumr + tot*tmp.sz; res.tot=tot+tmp.tot; res.sz=sz+tmp.sz; return res; } }T[N<<2],ans; inline void pushdown(ull o,ull l,ull r) { if(!T[o].add) return; if(l!=r) T[o<<1].add+=T[o].add,T[o<<1|1].add+=T[o].add; T[o].upd(); } ull ql,qr; ull v; void build(ull o,ull l,ull r) { T[o].sz=r-l+1; if(l==r) return; ull mid=l+r>>1; build(o<<1,l,mid); build(o<<1|1,mid+1,r); } void change(ull o,ull l,ull r) { pushdown(o,l,r); if(qr<l||ql>r) return; if(l>=ql&&r<=qr) { T[o].add+=v; pushdown(o,l,r); return; } ull mid=l+r>>1; change(o<<1,l,mid); change(o<<1|1,mid+1,r); T[o]=T[o<<1]+T[o<<1|1]; } void query(ull o,ull l,ull r) { pushdown(o,l,r); if(qr<l||ql>r) return; if(l>=ql&&r<=qr) { ans=ans+T[o]; return; } ull mid=l+r>>1; query(o<<1,l,mid); query(o<<1|1,mid+1,r); T[o]=T[o<<1]+T[o<<1|1]; } ull gcd(ull x,ull y) { return y ? gcd(y,x%y) : x; } int main() { n=read(),m=read(); build(1,1,n-1); char s[7]; while(m--) { scanf("%s",s); ql=read(),qr=read()-1; if(s[0]=='C') v=read(),change(1,1,n-1); else { ans.clr(); query(1,1,n-1); ull b=(qr-ql+2)*(qr-ql+1)/2,g=gcd(ans.sum,b); //b不是(qr-ql+1)*(qr-ql)/2是因为之前qr有-1 printf("%lld/%lld\n",ans.sum/g,b/g); } } return 0; }