[POI 2012]Kinoman(线段树)

题目链接

http://main.edu.pl/en/archive/oi/22/kin

题目大意

共有m部电影,编号为1~m,第i部电影的好看值为w[i]。
在n天之中(从1~n编号)每天会放映一部电影,第i天放映的是第f[i]部。
你可以选择l,r(1<=l<=r<=n),并观看第l,l+1,…,r天内所有的电影。如果同一部电影你观看多于一次,你会感到无聊,于是无法获得这部电影的好看值。所以你希望最大化观看且仅观看过一次的电影的好看值的总和。

思路

我们可以维护一个长度为 n 的线段树,初始时,线段树中第i位保存的是第 [1,i] 天看电影的好看值。
为了得到这个初始的线段树,我们可以预处理出每个电影第一次放映的时间 last[i] ,以及第 x 天看的电影的下一次放映时间next[x],然后我们枚举每个电影,假如这个电影全程只在第 x 天放映过一次,那么显然[1,x],[1,x+1]...[1,n]这些好看值里都会包含一个 w[f[x]] ,而如果这个电影放映过多次,第一次是在第 x 天放映,第二次是next[x]天放映,那么 [1,x]...[1,next[x]] 这些好看值里都会包含一个 w[f[x]]

然后我们从第2天到第 n 天枚举时间t,在这个线段树的基础上维护第 [t,i] 天看电影的好看值。显然,从 [t1,i] 转变为 [t,i] [t,t]...[t,next[t1]1] 的好看值里就没有了 w[f[t]] (这些区间原本包含1个电影f[t-1],现在不包含了), [t,next[t1]]...[t,next[next[t1]]1] 的好看值里会新添加入 w[f[x]] (这些区间原本包含2个电影f[t-1],现在只包含了一个),每次维护完之后,用整个线段树的最大值去更新答案即可。

于是我们只需要维护一个能支持区间修改、区间求最值的线段树即可

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000

using namespace std;

typedef long long int LL;

LL addtag[MAXN<<2],maxtag[MAXN<<2],ans=0;

void pushdown(int o)
{
    addtag[o<<1]+=addtag[o];
    addtag[o<<1|1]+=addtag[o];
    maxtag[o<<1]+=addtag[o];
    maxtag[o<<1|1]+=addtag[o];
    addtag[o]=0;
}

void pushup(int o)
{
    maxtag[o]=max(maxtag[o<<1],maxtag[o<<1|1]);
}

void add(int o,int L,int R,int ql,int qr,LL val)
{
    if(ql<=L&&R<=qr)
    {
        addtag[o]+=val;
        maxtag[o]+=val;
        return;
    }
    int M=(L+R)>>1;
    pushdown(o);
    if(ql<=M) add(o<<1,L,M,ql,qr,val);
    if(qr>M) add(o<<1|1,M+1,R,ql,qr,val);
    pushup(o);
}

LL query(int o,int L,int R,int ql,int qr)
{
    if(ql<=L&&R<=qr)
        return maxtag[o];
    pushdown(o);
    int M=(L+R)>>1;
    LL ans=0;
    if(ql<=M) ans=max(ans,query(o<<1,L,M,ql,qr));
    if(qr>M) ans=max(ans,query(o<<1|1,M+1,R,ql,qr));
    return ans;
}

int n,m,f[MAXN],w[MAXN],last[MAXN],next[MAXN]; //next[i]=第i天放映的电影上一次什么时候放,last[i]=第i部电影上一次是什么时候放
//或者说last[i]=第i部电影第一次放映的时间

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&f[i]);
    for(int i=1;i<=m;i++) scanf("%d",&w[i]);
    for(int i=n;i>=1;i--)
    {
        next[i]=last[f[i]];
        last[f[i]]=i;
    }
    for(int i=1;i<=m;i++)
        if(last[i]) //第i个电影有放映过
        {
            if(!next[last[i]]) //第i个电影只放映过一次
                add(1,1,n,last[i],n,w[i]); //last[i]~n天为右端点的区间的最大权值和都要加上w[i]
            else //第i个电影放映过多次
                add(1,1,n,last[i],next[last[i]]-1,w[i]); //last[i]~next[last[i]]天为右端点d区间的最大权值和都会包含w[i]
        }
    for(int i=1;i<=n;i++) //当前线段树的第x个位置的值是[i,x]这段时间观看的得分
    {
        ans=max(ans,query(1,1,n,1,n));
        //然后从[i,x]这段的得分转变为[i+1,x]这段的得分
        int t=next[i];
        if(t)
        {
            add(1,1,n,i,t-1,-w[f[i]]);
            if(next[t]) add(1,1,n,t,next[t]-1,w[f[i]]);
            else add(1,1,n,t,n,w[f[i]]);
        }
        else add(1,1,n,i,n,-w[f[i]]);
    }
    printf("%lld\n",ans);
    return 0;
}
<think>好的,我现在需要帮助用户理解如何在线段树中实现区间查询操作。用户已经提到了参考内容中的几个引用,特别是引用[2],其中涉及线段树的基本概念和操作。首先,我应该回顾线段树的基本结构,然后详细说明区间查询的步骤。 首先,线段树是一种二叉树结构,每个节点代表一个区间。叶子节点对应单个元素,而非叶子节点则代表其子节点的合并区间。区间查询的关键在于如何有效地分解查询区间,并合并子节点的结果。 接下来,我需要考虑如何递归地处理查询。比如,当查询区间完全覆盖当前节点的区间时,直接返回该节点的值。如果查询区间部分覆盖当前节点的区间,则需要递归查询左右子节点,并将结果合并。同时,要处理延迟更新(如果有的话),但用户的问题可能暂时不需要涉及惰性传播,不过引用[1]提到了惰性段树,可能需要简要提及。 然后,我需要确保解释清楚递归的终止条件和合并结果的步骤。例如,在求和操作中,合并就是相加;在最小值操作中,合并就是取较小值。这部分需要具体说明,可能通过示例来展示。 用户提供的示例代码是关于快速排序的,但用户的问题是关于线段树的区间查询,所以需要给出线段树的相关代码示例。根据引用[2]中的内容,可能参考其中的实现方法。不过用户提供的引用[1]中的Python代码可能更适合作为例子,因为它是关于惰性段树的,但用户的问题可能不需要惰性部分,所以需要调整。 另外,需要注意用户提到的格式要求,尤其是数学表达式使用$...$,独立公式用$$...$$,并且生成相关问题。同时,引用需要正确标注,比如引用[^2]和[^3]。 可能的步骤: 1. 解释线段树的结构和区间查询的基本思想。 2. 分步骤说明递归查询的过程。 3. 给出具体的代码示例,比如求和或最小值的查询。 4. 处理可能的边界情况。 5. 提及惰性传播的扩展(如果有必要)。 6. 生成相关问题。 需要确保代码示例正确,并且符合Python语法,同时注释解释关键步骤。可能还需要对比不同的操作类型(如求和、最小值)在合并时的差异。 现在需要检查是否有遗漏的要点,比如区间分解的条件,递归的终止条件,以及合并结果的函数。同时,引用需要正确对应到参考内容中的相关部分,比如引用[2]讨论了线段树的基本操作,而引用[1]提到惰性传播,可能在优化部分提及。 最后,生成的相关问题需要与线段树的区间查询相关,比如优化方法、应用场景或与其他数据结构的比较。</think>### 线段树的区间查询操作实现步骤 线段树(Segment Tree)是一种用于高效处理**区间查询**(如区间求和、最小值、最大值等)和**区间更新**的数据结构。其核心思想是将区间递归分解为子区间,通过二叉树结构存储预处理结果,使得查询时间复杂度为$O(\log n)$。 #### 1. 线段树的结构 线段树是二叉树,每个节点表示一个区间$[l, r]$: - **叶子节点**:区间长度为1(如$[3,3]$),直接存储对应元素值。 - **非叶子节点**:存储子区间合并后的结果(如区间和、最小值等)。 - **合并函数**:根据需求定义(如求和用加法,最小值用$\min$)。 #### 2. 区间查询步骤 假设要查询区间$[q_l, q_r]$的值,递归过程如下: 1. **完全覆盖**:若当前节点区间$[l, r]$完全包含在$[q_l, q_r]$中,直接返回节点值。 2. **部分重叠**:否则,将查询分解到左右子节点: - 计算中点$mid = \lfloor (l + r) / 2 \rfloor$ - 若$q_l \leq mid$,递归查询左子树。 - 若$q_r > mid$,递归查询右子树。 3. **合并结果**:将左右子树查询结果按合并函数计算最终值。 #### 3. 代码示例(区间求和查询) ```python class SegmentTree: def __init__(self, data): self.n = len(data) self.tree = [0] * (4 * self.n) # 初始化树结构 self.data = data self.build(0, 0, self.n - 1) def build(self, node, l, r): if l == r: self.tree[node] = self.data[l] else: mid = (l + r) // 2 left = 2 * node + 1 right = 2 * node + 2 self.build(left, l, mid) self.build(right, mid+1, r) self.tree[node] = self.tree[left] + self.tree[right] # 合并为区间和 def query_range(self, q_l, q_r): return self._query(0, 0, self.n - 1, q_l, q_r) def _query(self, node, l, r, q_l, q_r): if r < q_l or l > q_r: return 0 # 区间无交集,返回不影响合并的值(如求和时为0) if q_l <= l and r <= q_r: return self.tree[node] # 完全覆盖 mid = (l + r) // 2 left_sum = self._query(2*node+1, l, mid, q_l, q_r) right_sum = self._query(2*node+2, mid+1, r, q_l, q_r) return left_sum + right_sum # 合并左右子树结果 # 示例用法 data = [1, 3, 5, 7, 9] st = SegmentTree(data) print(st.query_range(1, 3)) # 输出3+5+7=15 ``` #### 4. 关键点说明 - **时间复杂度**:每次查询最多遍历树高$O(\log n)$。 - **合并函数**:可根据需求修改,如求最小值时替换为`min(left_min, right_min)`。 - **惰性传播(可选)**:若支持区间更新,需引入延迟标记(Lazy Propagation)优化性能[^1]。 #### 5. 应用场景 - 动态区间统计:实时游戏积分榜排名、股票区间波动分析。 - 范围过滤:地图应用中搜索某个矩形区域内的POI点。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值