【Luogu P2221】 [HAOI2012]高速公路

本文介绍了一种通过线段树来解决高速公路收费问题的方法。具体来说,通过对边权值的处理和线段树的有效利用,实现了对特定路段收费策略的快速更新及查询。此算法能够高效计算出指定区间的总费用,并通过数学推导简化了合并子树信息的过程。
摘要由CSDN通过智能技术生成

题目链接

题目描述

Y901高速公路是一条重要的交通纽带,政府部门建设初期的投入以及使用期间的养护费用都不低,因此政府在这条高速公路上设立了许多收费站。

Y901高速公路是一条由N-1段路以及N个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为1~N,从收费站i行驶到i+1(或从i+1行驶到i)需要收取Vi的费用。高速路刚建成时所有的路段都是免费的。

政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。

无聊的小A同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的l,r(l

题解

首先注意一点:权值是边的,一次操作是将l,r之间的边的权值增加,和点没有一点关系。(不然你连样例都玩不出来 QAQ)

然后是做法:
既然每种情况的概率是相同的,且有 C(n2) C ( 2 n ) 种。
那么就只需要求出区间l到r的所有路径的权值和。
对区间操作,考虑线段树。
首先为了方便,把边的权值扔到他前面那一个点上,且由于道路可逆,我们先只靠虑从编号小的到编号大的路径,之后乘上2就好。
对于一段长为L的区间,他第一个点的权值被算了 L1 L − 1 次,第二点被算了 2(L2) 2 ∗ ( L − 2 ) 次。
手玩发现有如下公式。(Sum为该区间种路径的权值和,v为权值)

Sum=i=1Li(Li)v[i] S u m = ∑ i = 1 L i ∗ ( L − i ) ∗ v [ i ]

我们用的是线段树,所以不用管这个怎么快速算出来,只需管如何合并子树信息与答案即可。
维护一个答案ans,考虑怎么合并。边界很好搞,不多说。
nowlsrs 设 当 前 区 间 是 n o w , 左 儿 子 是 l s , 右 儿 子 是 r s
ls,Liv[i]rs,LilsL1rsL2rs(L1+i)(L2+L1iL1)v[i] 对 于 l s , 公 式 中 变 化 的 只 有 L , 于 是 发 现 我 们 要 维 护 ∑ i ∗ v [ i ] 。 再 来 看 r s , 变 化 的 是 L 与 i , 设 l s 长 度 为 L 1 , r s 长 度 为 L 2 , r s 的 式 子 变 成 ∑ ( L 1 + i ) ∗ ( L 2 + L 1 − i − L 1 ) ∗ v [ i ]
(L1+i)(L2i)v[i],L1(L2i)v[i] 即 ∑ ( L 1 + i ) ∗ ( L 2 − i ) ∗ v [ i ] , 比 原 来 多 了 ∑ L 1 ∗ ( L 2 − i ) ∗ v [ i ]
发现只要再维护一个 (L2i)v[i] ∑ ( L 2 − i ) ∗ v [ i ]
于是我们先看怎么维护 iv[i]S ∑ i ∗ v [ i ] , 设 为 S ,这个在合并 lsrsS[now]=S[ls]+S[rs]+L1sum[rs](sum) l s 与 r s 时 , S [ n o w ] = S [ l s ] + S [ r s ] + L 1 ∗ s u m [ r s ] ( s u m 是 区 间 内 的 权 值 和 )
再看 (L2i)v[i]S2: ∑ ( L 2 − i ) ∗ v [ i ] , 设 为 S 2 , 维 护 也 简 单 , 与 上 面 类 似 。 但 我 们 发 现 :
S[now]+S2[now]=Lsum[now]sum S [ n o w ] + S 2 [ n o w ] = L ∗ s u m [ n o w ] , 所 以 其 实 没 必 要 维 护 这 个 , 维 护 s u m 直 接 算 就 好 了 。
让后还要注意区间长度是看询问的…QAQ

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
typedef long long ll;
const int N=2e5+10;
struct node{
    ll sum;ll S;
    ll la;ll ans;
    void clear(){sum=S=la=ans=0;}
}tr[N<<2];
#define ls (now<<1)
#define rs (now<<1|1)
ll SD1[N];
ll SD2[N];//在区间修改时预处理的数组
//SD1=1+2+3+....+n
//SD2=sigma(i*(L-i))
int n,m;
inline ll gcd(ll a,ll b){return (b==0)? a:gcd(b,a%b);}
inline void push(int now,int x,int len)
{
    tr[now].la+=x;tr[now].sum+=1ll*len*x;
    tr[now].ans+=SD2[len]*x;tr[now].S+=SD1[len]*x;//自己想想,很好理解
}
inline void push_down(int now,int l,int r)
{
    if(!tr[now].la) return ;
    register int mid=l+r>>1;
    push(ls,tr[now].la,mid-l+1);push(rs,tr[now].la,r-mid);
    tr[now].la=0;
}
inline void updata(int now,int llen,int rlen)
{
    tr[now].sum=tr[ls].sum+tr[rs].sum;
    tr[now].S=tr[ls].S+tr[rs].S+tr[rs].sum*llen;
    tr[now].ans=tr[ls].ans+tr[rs].ans+tr[ls].S*rlen+(1ll*rlen*tr[rs].sum-tr[rs].S)*llen;
}
inline void add(int now,int l,int r,int L,int R,int x)
{
    push_down(now,l,r);
    if(l>=L&&r<=R) {push(now,x,r-l+1);return;}
    register int mid=l+r>>1;
    if(mid>=L) add(ls,l,mid,L,R,x);
    if(mid<R) add(rs,mid+1,r,L,R,x);
    updata(now,mid-l+1,r-mid);
}
inline node query(int now,int l,int r,int L,int R)
{
    push_down(now,l,r);
    if(l>=L&&r<=R) return tr[now];
    register int mid=l+r>>1;
    if(mid>=R) return query(ls,l,mid,L,R);
    else if(mid<L) return query(rs,mid+1,r,L,R);
    node p1=query(ls,l,mid,L,mid);node p2=query(rs,mid+1,r,mid+1,R);
    register int llen=mid-L+1;register int rlen=R-mid;//!!区间长度注意是跟询问的有关的(不是根据当前区间确定)
    node res={0,0,0,0};
    res.sum=p1.sum+p2.sum;
    res.S=p1.S+p2.S+p2.sum*llen;
    res.ans=p1.ans+p2.ans+p1.S*rlen+(1ll*rlen*p2.sum-p2.S)*llen;
    return res;
}
int main()
{
    n=read();m=read();
    register ll base=1;SD1[1]=1;
    for(register int i=2;i<=n;i++){
        base+=i;SD1[i]=base;SD2[i]=SD2[i-1]+SD1[i-1];
    }
    char ch;
    for(register int i=1;i<=m;i++)
    {
        ch=getchar();register int l,r;
        while(ch!='C'&&ch!='Q') ch=getchar();
        if(ch=='Q'){
            l=read();r=read();
            register ll d=r-l+1;register ll SI=d*(d-1);
            register ll SUM=2*(query(1,1,n,l,r)).ans;
            ll g=gcd(SUM,SI);
            SUM/=g;SI/=g;
            printf("%lld",SUM);
            putchar('/');printf("%lld\n",SI);
        }
        else{
            l=read();r=read();r--;
            register int x=read();
            add(1,1,n,l,r,x);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值