UVA1400----线段树的单点更新(十分的搞人啊)

本文介绍了如何利用线段树数据结构高效地解决给定条件下的查询问题,包括前缀和、后缀和及区间最大连续和的计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目地址:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=501&page=show_problem&problem=4146

题意:

给你n个数字,然后有Q次查询

每次查询,给出两个数字a,b

要你找出满足a<=x<=y<=b的条件下,【x,y】的连续和最大

因为有多次查询,所以用暴力的方法肯定承受不了

而线段树可以解决这个问题

刘汝佳的那本大白上有一点介绍,但是我觉得那个说的不完全,故我再说一遍

首先是建树的问题

我们先用一个pre_sum的数组来表示前i个数字的和,那么要求i,j的和直接用pre_sum[j]-pre[i-1]就可以了

然后就是建树了,我们还要用到三个一个是sub(x,y),一个是前缀prex,一个是后缀suffx

分别是什么意思呢?

sub(x,y)表示的在一个(x,y)的区间中的连续子段和的左右端点,所以这是一个pair结构

prex表示的在一个区间(a,b)中从左端点开始的最大子段和的右端点

suffx表示的在一个区间(a,b)中从右端点往左的最大子段和的左端点

然后在线段树中这三个变量是可以互相递推得到的

首先是sub,对于左子树和右子树来说,一共就三种情况,左子树的sub,右子树sub,以及左子树的后缀和加上右子树的前缀和

然后根据这三个的和来决定当前的sub取哪个

然后就是前缀prex,对于左子树和右子树来说,就是两种情况,直接就是左子树的前缀,以及左子树的前缀加上右子树的前缀

然后就是后缀suffx,也是两种情况,直接就是右子树的后缀,或者是右子树的全部加上左子树的后缀

根据各自的和的大小来决策递推,这样就可以把树建好。由叶子节点网上递推

我们把树建好了之后就是要查询了。

对于每次查询,例如区间[a,b]

如果正好该区间是一个完整的,也就是不是被两棵子树截断的,那么直接就是该树的sub了

如果被截断,怎么办?

那么我们就是要查出左子树的后缀,右子树的前缀,以及左右子树的sub

左右子树最好的sub构成了一个答案区间,但是这还没完,我们可以根据刚刚求出的左子树的后缀加上右子树的前缀构成另外一个新的解答区间

然后再比较这两个区间,看哪个更加好,选择,即为答案。

至于怎么查前缀和后缀,在代码里面,这里不再赘述。上代码:



#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;

#define LL long long
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int maxn =  500000;
LL pre_sum[maxn];
typedef pair<int,int> PP;
struct node
{
    LL prex;
    LL suffx;
    PP sub;
}tree[maxn<<2];
int n,q;

void read()
{
    pre_sum[0] = 0;
    int a;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a);
        pre_sum[i] = pre_sum[i-1]+a;
    }
}

LL summ(int l,int r)
{
    return pre_sum[r]-pre_sum[l-1];
}

LL summ(PP a)
{
    return pre_sum[a.second] - pre_sum[a.first-1];
}

PP judge(PP a,PP b)
{
    if(summ(a) != summ(b))
        return summ(a)>summ(b)?a:b;
    return a<b?a:b;//pair 自带的字典序比较
}

void pushup(int l,int r,int rt)
{
    //递推sub
    int m = (l+r)>>1;
    int lc = rt<<1;
    int rc = rt<<1|1;
    tree[rt].sub = judge(tree[lc].sub,tree[rc].sub);
    tree[rt].sub = judge(tree[rt].sub,make_pair(tree[lc].suffx,tree[rc].prex));

    //递推prex
    LL lc_pre = summ(l,tree[lc].prex);
    LL lc_rc_pre = summ(l,tree[rc].prex);
    if(lc_pre == lc_rc_pre)
        tree[rt].prex = tree[lc].prex;
    else
        tree[rt].prex = lc_pre > lc_rc_pre ? tree[lc].prex : tree[rc].prex;

    //递推suffx
    LL rc_suff = summ(tree[rc].suffx,r);
    LL lc_rc_suff = summ(tree[lc].suffx,r);
    if(rc_suff == lc_rc_suff)
        tree[rt].suffx = tree[lc].suffx;
    else
        tree[rt].suffx = rc_suff > lc_rc_suff ? tree[rc].suffx : tree[lc].suffx;

}

void build(int l,int r,int rt)
{
    if(l==r)
    {
        tree[rt].prex = tree[rt].suffx = l;
        tree[rt].sub = make_pair(l,l);
        return;
    }

    int m = (l+r)>>1;
    build(lson);
    build(rson);
    pushup(l,r,rt);
}

PP query_pre(int l,int r,int rt,int L,int R)
{
    if(tree[rt].prex <= R)
        return make_pair(L,tree[rt].prex);
    int m = (l+r)>>1;
    if(R<=m)
        return query_pre(lson,L,R);
    PP t = query_pre(rson,L,R);
    t.first = l;
    return judge(t,make_pair(l,tree[rt<<1].prex));
}

PP query_suff(int l,int r,int rt,int L,int R)
{
    if(tree[rt].suffx >=L)
        return make_pair(tree[rt].suffx,R);
    int m = (l+r)>>1;
    if(L>m)
        return query_suff(rson,L,R);
    PP t = query_suff(lson,L,R);
    t.second = r;
    return judge(t,make_pair(tree[rt<<1|1].suffx,r));
}

PP query(int l,int r,int rt,int L,int R)
{
    if(L<=l && r<=R)
        return tree[rt].sub;
    int m = (l+r)>>1;
    if(R<=m)
        return query(lson,L,R);
    if(L>m)
        return query(rson,L,R);
    PP t1 = query_pre(rson,L,R);
    PP t2 = query_suff(lson,L,R);
    PP t3 = judge(query(lson,L,R),query(rson,L,R));
    return judge(make_pair(t2.first,t1.second),t3);
}

int main()
{
    int ca = 1;
    while(~scanf("%d%d",&n,&q))
    {
        read();
        build(1,n,1);
        int L,R;
        printf("Case %d:\n",ca++);
        while(q--)
        {
            scanf("%d%d",&L,&R);
            PP ans = query(1,n,1,L,R);
            printf("%d %d\n",ans.first,ans.second);
        }
    }
    return 0;
}



#include <bits/stdc++.h> #define ll long long #define lb(x) x&(-x) using namespace std; const ll N=2e5; struct node{ ll l,r,suml,sumr,ans; }tr[4*N+5]; ll n,m,t; ll yy[N+5],pp[N+5],r[N+5],rr[N+5]; ll get(ll x,ll y){ ll s=0,ss=0; // cout<<x<<" "<<y<<" "; for(ll i=y-1;i;i-=lb(i)){ s+=rr[i]; } for(ll i=x-1;i;i-=lb(i)){ ss+=rr[i]; } // cout<<s-ss<<endl; return s-ss; } void updata(ll p,ll l,ll r){ ll mid=(l+r)>>1; tr[p].suml=min(tr[2*p].suml+get(mid,r),tr[2*p+1].suml); tr[p].sumr=min(tr[2*p+1].sumr+get(l,mid+1),tr[2*p].sumr); tr[p].ans=tr[2*p].suml+tr[2*p+1].sumr+get(mid,mid+1); tr[p].ans=min(tr[p].ans,min(tr[2*p].ans,tr[2*p+1].ans)); return; } void build(ll p,ll l,ll r){ if(l==r){ tr[p].l=l; tr[p].r=r; tr[p].suml=yy[l]; tr[p].sumr=pp[l]; tr[p].ans=yy[l]+pp[l]; return; } ll mid=(l+r)>>1; build(2*p,l,mid); build(2*p+1,mid+1,r); tr[p].l=tr[2*p].l; tr[p].r=tr[2*p+1].r; updata(p,tr[p].l,tr[p].r); return; } void change(ll p,ll x){ if(tr[p].l==tr[p].r){ tr[p].suml=yy[x]; tr[p].sumr=pp[x]; tr[p].ans=yy[x]+pp[x]; return; } ll mid=(tr[p].l+tr[p].r)>>1; if(x<=mid) change(2*p,x); else change(2*p+1,x); updata(p,tr[p].l,tr[p].r); return; } int main(){ scanf("%lld%lld%lld",&n,&m,&t); for(ll i=1;i<=n;i++) scanf("%lld",&yy[i]); for(ll i=1;i<=n;i++) scanf("%lld",&pp[i]); for(ll i=1;i<n;i++){ scanf("%lld",&r[i]); for(ll j=i;j<n;j+=lb(j)) rr[j]+=r[i]; } build(1,1,n); // for(ll i=1;i<=40;i++){ // cout<<tr[i].l<<" "<<tr[i].r<<" "; // cout<<tr[i].suml<<" "<<tr[i].sumr<<" "<<tr[i].ans<<endl; // } // return 0; printf("%lld\n",tr[1].ans); while(m--){ char op; ll x,y; cin>>op; scanf("%lld%lld",&x,&y); if(op=='r'){ for(ll i=x;i<n;i+=lb(i)){ rr[i]-=r[x]; rr[i]+=y; } r[x]=y; change(1,x),change(1,x+1); } else if(op=='p'){ pp[x]=y; change(1,x); } else{ yy[x]=y; change(1,x); } printf("%lld\n",tr[1].ans); } return 0; } ## **题目描述** 阿杰在游戏中管理着 $n$ 座编号为 $1$ 至 $n$ 的城市。每座城市有一个矿场和一个发电厂。他需要完成以下过程: 1. **采矿**:在任意城市 $i$ 的矿场开采矿物,花费 $y_i$(可为负,表示收益)。 2. **运输**:通过道路将矿物运送到任意城市 $j$ 的发电厂。相邻城市 $i$ 与 $i+1$ 间的道路费用为 $r_i$(双向且相同,可为负但不允许重复经过同一条道路)。 3. **发电**:在城市 $j$ 的发电厂消耗矿物发电,花费 $p_j$(可为负)。 **总花费**为三部分之和:$y_i + p_j + \text{运输路径费用}$。目标是选择起点 $i$ 和终点 $j$,使总花费最小(负花费表示净收益)。 游戏会动态更新费用:共 $m$ 个事件,每个事件将某个 $y_i$、$p_i$ 或 $r_i$ 修改为 $c$。需要实时输出每次更新后的最小总花费。 ## **输入格式** - **第一行**:$n, m, T$(城市数、事件数、测试点编号)。 - **第二行**:$n$ 个整数 $y_1, y_2, \dots, y_n$(采矿费用)。 - **第三行**:$n$ 个整数 $p_1, p_2, \dots, p_n$(发电费用)。 - **第四行**:$n-1$ 个整数 $r_1, r_2, \dots, r_{n-1}$(道路费用,$r_i$ 连接城市 $i$ 和 $i+1$)。 - **接下来 $m$ 行**:每行格式为 `t i c`: - $t \in \{ \texttt{y}, \texttt{p}, \texttt{r} \}$ 表示修改的费用类型。 - $i$ 表示被修改的城市编号($t=\texttt{r}$ 时 $i$ 表示道路编号,范围 $1 \le i \le n-1$)。 - $c$ 为新费用值(绝对值 $\le 10^{12}$)。 ## **输出格式** - **共 $m+1$ 行**: - 第 $1$ 行:初始状态的最小花费。 - 第 $2$ 至 $m+1$ 行:每次事件后的最小花费。 ## **样例** #### 输入样例 ```plaintext 10 10 0 12 12 17 28 42 55 60 73 73 91 89 89 79 77 54 38 34 24 24 0 2 4 2 1 4 3 7 10 10 p 2 92 p 3 87 r 4 3 r 1 9 r 2 4 r 5 3 y 10 72 y 8 67 r 2 0 y 4 91 ``` #### 输出样例 ```plaintext 53 53 53 55 55 55 54 54 54 50 50 ``` ## **数据范围** | 测试点 | $n$ | $m$ | 费用可为负 | $y_i=0$ | $p_i=0$ | |:------:|:-----:|:-------:|:------:|:----:|:-------:| | 1 | $500$ | $500$ | 否 | 否 | 否 | | 2 | $500$ | $500$ | 是 | 否 | 否 | | 3 | $3000$ | $3000$ | 否 | 否 | 否 | | 4 | $3000$ | $3000$ | 是 | 否 | 否 | | 5 | $2\times10^5$ | $2\times10^5$ | 是 | 是 | 是 | | 6–10 | $2\times10^5$ | $2\times10^5$ | 混合 | 混合 | 混合 | **特殊约束**: - 若“费用可为负”为**否**,则所有费用(含修改后)$\ge 0$。 - 若 $y_i=0$ 为**是**,则所有 $y_i=0$ 且无 `y` 类型事件;$p_i=0$ 同理。 --- ### **题目补充说明** 1. **道路特性**: - 道路双向通行且费用相同,但**不允许重复使用**(即使 $r_i<0$ 也不能多次经过同一条路获利)。 - 运输路径是简单路径(无环路)。 2. **发电过程**: - 采矿和发电必须在**不同或相同城市**完成(即 $i$ 和 $j$ 可相同,此时运输费用为 $0$)。 - 总花费计算式:$\text{min}_{1 \le i,j \le n} \left\{ y_i + p_j + \text{dis}(i,j) \right\}$,其中 $\text{dis}(i,j)$ 是 $i$ 到 $j$ 的路径费用和。 3. **事件限制**: - `r i c` 事件中 $i$ 为道路编号($1 \le i \le n-1$),修改连接 $i$ 和 $i+1$ 的道路费用。 代码答案错误,找出问题
08-03
资源下载链接为: https://pan.quark.cn/s/d9ef5828b597 在本文中,我们将探讨如何通过 Vue.js 实现一个带有动画效果的“回到顶部”功能。Vue.js 是一款用于构建用户界面的流行 JavaScript 框架,其组件化和响应式设计让实现这种交互功能变得十分便捷。 首先,我们来分析 HTML 代码。在这个示例中,存在一个 ID 为 back-to-top 的 div 元素,其中包含两个 span 标签,分别显示“回到”和“顶部”文字。该 div 元素绑定了 Vue.js 的 @click 事件处理器 backToTop,用于处理点击事件,同时还绑定了 v-show 指令来控制按钮的显示与隐藏。v-cloak 指令的作用是在 Vue 实例渲染完成之前隐藏该元素,避免出现闪烁现象。 CSS 部分(backTop.css)主要负责样式设计。它首先清除了一些默认的边距和填充,对 html 和 body 进行了全屏布局,并设置了相对定位。.back-to-top 类则定义了“回到顶部”按钮的样式,包括其位置、圆角、阴影、填充以及悬停时背景颜色的变化。此外,与 v-cloak 相关的 CSS 确保在 Vue 实例加载过程中隐藏该元素。每个 .page 类代表一个页面,每个页面的高度设置为 400px,用于模拟多页面的滚动效果。 接下来是 JavaScript 部分(backTop.js)。在这里,我们创建了一个 Vue 实例。实例的 el 属性指定 Vue 将挂载到的 DOM 元素(#back-to-top)。data 对象中包含三个属性:backTopShow 用于控制按钮的显示状态;backTopAllow 用于防止用户快速连续点击;backSeconds 定义了回到顶部所需的时间;showPx 则规定了滚动多少像素后显示“回到顶部”按钮。 在 V
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值