HAOI2012 高速公路 线段树
题目描述
输入格式
输出格式
样例
数据范围与提示
首先这个题一看就是一道数据结构题,废话,考虑一下这个题要求什么,一般用分数表示的概率期望都是假的,不要问我为什么。。这道题其实就是要找一个子区间的和,再除以选端点的方案数$C_{len}^{2}$,那么好像暴力只能用$O(n^3)$打,毕竟枚举子区间就要$n^2$的。
我们现在要考虑暴力如何优化,这道题和HAOI的另一道题树上染色的思路很像,那个题中两点距离通过找边的贡献得到,进而把枚举两点变做加边贡献,那么这个题也从这个思路考虑,枚举每一段路的贡献,那么我们就的到了$O(n)$修改/查询的式子:
$\sum \limits_{i=l}^{r} s[i]*(r-i+1)*(i-l+1)$
这个使我们成功转化思路,这样的优化暴力能水过90分,已经非常优秀了。如果要A过,我们考虑用线段树去维护这个东西,我们首先的对这个式子变形,目的是得到可以区间合并的并且log查询的东西。我们先把括号都拆掉。
$\sum \limits_{i=l}^{r} s[i]*i*(l+r)-s[i]*i^{2}-s[i]*(l-1)*(r+1)$
然后把常数提出来,能一起维护的放在一起。
$(l+r)*\sum \limits_{i=l}^{r} s[i]*i-\sum \limits_{i=l}^{r}s[i]*i^{2}-(l-1)(r+1)*\sum \limits_{i=l}^{r}s[i]$
那么我们已经发现,此时我们对于一个区间,只需要维护它的$s[i]$,$s[i]*i^{2}$,$s[i]*i$就可以了。
查询已经解决,那么如何修改呢,$s[i]$,$s[i]*i$都比较简单,一个是加$w*1$,一个是$s[i]*i+w*i$等差数列即可,最后是$s[i]*i^{2}+w*i^{2}$,这个平方就要用到自然数方幂和公式,借此机会习得拉格朗日插值。当然只要知道公式就可以了$n(n+1)(2n+1)/6$。
友情提示,懒标记是用来叠加的,不是用来覆盖的。。。。
#include<iostream> #include<cstdio> #define ll long long #define int long long using namespace std; const int N=1000020,M=100020; struct node { ll s,si,sii; node operator +(node b) { node c;c.s=c.si=c.sii=0; c.s=s+b.s; c.si=si+b.si; c.sii=sii+b.sii; return c; } }; struct tree{int l,r;ll f;node s;}tr[4*N]; int rd() { int s=0,w=1; char cc=getchar(); while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } void change(int k,int w) { int l=tr[k].l,r=tr[k].r; tr[k].s.s+=1ll*w*(r-l+1); tr[k].s.si+=1ll*w*((l+r)*(r-l+1)/2); tr[k].s.sii+=1ll*w*((r*(r+1)*(2*r+1)-(l-1)*l*(2*l-1))/6); tr[k].f+=w; } void down(int k) { change((k<<1),tr[k].f); change((k<<1|1),tr[k].f); tr[k].f=0; } void build(int k,int l,int r) { tr[k].l=l;tr[k].r=r; if(l==r) { tr[k].f=tr[k].s.si=tr[k].s.sii=tr[k].s.s=0; return ; } int mid=(l+r)>>1; build(k<<1,l,mid);build(k<<1|1,mid+1,r); tr[k].s=tr[k<<1].s+tr[k<<1|1].s; } node ask(int k,int x,int y) { //cout<<tr[k].l<<" "<<tr[k].r<<" "<<x<<" "<<y<<" "<<tr[k].s.s<<endl; int l=tr[k].l,r=tr[k].r,mid=(l+r)>>1; if(tr[k].f) down(k); if(x==l&&r==y) return tr[k].s; if(y<=mid) return ask(k<<1,x,y); else if(x>mid) return ask(k<<1|1,x,y); return ask(k<<1,x,mid)+ask(k<<1|1,mid+1,y); } void add(int k,int x,int y,int w) { //cout<<k<<" "<<x<<" "<<y<<" "<<tr[k].s.s<<endl; int l=tr[k].l,r=tr[k].r,mid=(l+r)>>1; if(tr[k].f) down(k); if(x==l&&r==y) { change(k,w); return; } if(y<=mid) add(k<<1,x,y,w); else if(x>mid) add(k<<1|1,x,y,w); else add(k<<1,x,mid,w),add(k<<1|1,mid+1,y,w); tr[k].s=tr[k<<1].s+tr[k<<1|1].s; } ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} signed main() { //freopen("data.in","r",stdin); //freopen("data.out","w",stdout); int n=rd()-1,m=rd();ll ans=0,len,d; char op[2]; build(1,1,n); for(int i=1,x,y,z;i<=m;i++) { scanf("%s",op); if(op[0]=='C') { x=rd(),y=rd()-1,z=rd(); //cout<<i<<" "<<x<<" "<<y<<endl; add(1,x,y,z); // cout<<ask(1,31,39).s<<endl; continue; } if(op[0]=='Q') { x=rd(),y=rd()-1; // cout<<x<<" "<<y<<endl; node tmp=ask(1,x,y); ans=(tmp.si*(x+y)-tmp.sii-1ll*(x-1)*(y+1)*tmp.s); //cout<<ask(1,1,2).s<<endl; //cout<<tmp.s<<" "<<tmp.si*(x+y)<<" "<<tmp.sii<<" "<<(x-1)*(y+1)*tmp.s<<endl; len=1ll*(y-x+2)*(y-x+1)/2; if(ans==0) puts("0/1"); else d=gcd(ans,len),printf("%lld/%lld\n",ans/d,len/d); } } } /* g++ 1.cpp -o 1 ./1 4 5 C 1 4 2 C 1 2 -1 Q 1 2 Q 2 4 Q 1 4 */