《算法竞赛·快冲300题》每日一题:“区间开根”

算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。


区间开根” ,链接: http://oj.ecustacm.cn/problem.php?id=1825

题目描述

【题目描述】 给定长度为n的数组a,存在m次操作:
  1 l r:询问区间[l,r]数字之和
  2 l r:将区间[l,r]的每个数字均开根号向下取整。
  例如:数组a为1 100 5 5,对区间[1,4]开根号,整个数组变成:1 10 2 2。
  对于每次操作1输出对应询问的结果。
【输入格式】 第一行为正整数n,1≤n≤100000。
  第二行包含n个整数,表示数组a,0≤a[i]≤10^9。
  第三行为整数m,m≤200000。
  接下来m行每行三个整数,格式如题目描述x l r,x等于1或者2,l≤r。
【输出格式】 对于每次询问,输出对应询问结果。
【输入样例】

4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4

【输出样例】

101
11
11

题解

  题目要求对数组进行区间修改、区间查询,显然是考察对基础线段树应用的掌握。编码基本上可以套用区间修改的模板。
  线段树的学习,参考《算法竞赛》第4章“4.3 线段树”,或者看博客https://blog.csdn.net/weixin_43914593/article/details/108221534
  区间修改需要用到懒惰标记(lazy-tag)技术。在使用lazy-tag的基础上完成本题的修改和查询,计算复杂度是O(mlogn)。
  不过本题不一定需要使用标准的lazy-tag操作。因为本题的区间修改是开根号,实际上每个数字从最大的a[i]= 1 0 9 10^9 109开始只需要开5次根号就变成了1。也就是说,任意的一个区间,最多做5次区间修改,内部所有数字都会变成1。那么只要把原来判断区间lazy-tag,换成判断区间的最值是不是1,同样能达到快速区间操作的目的,最多只慢了5倍。经过这个简化,不用再对lazy-tag编码,代码少了很多。
【重点】 基本的线段树模板。

C++代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N];     //记录数列的元素,从a[1]开始
int ma[N << 2];   //区间最值
ll sum[N << 2];   //区间和
void push_up(int p){
    ma[p] = max(ma[p << 1], ma[p << 1 | 1]); //区间最值
    sum[p] = sum[p << 1] + sum[p << 1 | 1];  //区间和
}
void build(int p, int pl, int pr){
    if(pl == pr) {    //最底层的叶子,赋值
        ma[p] = sum[p] = a[pl];
        return;
    }
    int mid = (pl + pr) >> 1;//分治:折半
    build(p << 1, pl, mid);//左儿子
    build(p << 1 | 1, mid + 1, pr);//右儿子
    push_up(p);//从下往上传递区间值
}
//把区间[L,R]开根号
void update(int p, int pl, int pr, int L, int R){ //区间修改:把[L, R]内每个元素取根
    if(ma[p] <= 1) return;  //区间[L,R]最大值不超过1,说明整个区间均为1或者0
    if(pl == pr) {    //叶子结点
        ma[p] = sum[p] = sqrt(ma[p]);
        return;
    }
    int mid = (pl + pr) >> 1;
    if(L <= mid)  update(p << 1, pl, mid, L, R);
    if(R > mid)   update(p << 1 | 1, mid + 1, pr, L, R);
    push_up(p);
}

//查询区间[L,R]的和
ll query(int p, int pl, int pr, int L, int R){
    if(L <= pl && pr <= R)   return sum[p];
    int mid = (pl + pr) >> 1;
    ll res = 0;
    if(L <= mid) res += query(p << 1, pl, mid, L, R);
    if(R > mid)  res += query(p << 1 | 1, mid + 1, pr, L, R);
    return res;
}

int main(){
    int n; cin >> n;
    for(int i = 1; i <= n; i++)   scanf("%d",&a[i]);   //scanf比cin快
    build(1, 1, n);
    int m; cin >> m;
    while(m--)    {
        int op, L, R; scanf("%d%d%d",&op,&L,&R);      
        if(op == 1)   printf("%lld\n",query(1,1,n,L,R));
        else update(1, 1, n, L, R);
    }
    return 0;
}

Java代码

import java.util.*;
public class Main{
   static int[] a; //记录数列的元素,从a[1]开始
   static int[] ma; //区间最值
   static long[] sum; //区间和
   public static void main(String[] args){
       Scanner scanner = new Scanner(System.in);
       int n = scanner.nextInt();
       a = new int[n + 1];
       for(int i = 1; i <= n; i++)    a[i] = scanner.nextInt();
       ma = new int[n << 2];
       sum = new long[n << 2];
       build(1, 1, n);
       int m = scanner.nextInt();
       while(m-- > 0){
           int op = scanner.nextInt();
           int L = scanner.nextInt();
           int R = scanner.nextInt();
           if(op == 1)  System.out.println(query(1, 1, n, L, R));
           else         update(1, 1, n, L, R);
       }
   }
   static void push_up(int p){
       ma[p] = Math.max(ma[p << 1], ma[p << 1 | 1]); //区间最值
       sum[p] = sum[p << 1] + sum[p << 1 | 1]; //区间和
   }
   static void build(int p, int pl, int pr){
       if(pl == pr){ //最底层的叶子,赋值
           ma[p]  = a[pl];
           sum[p] = a[pl];
           return;
       }
       int mid = (pl + pr) >> 1; //分治:折半
       build(p << 1, pl, mid); //左儿子
       build(p << 1 | 1, mid + 1, pr); //右儿子
       push_up(p); //从下往上传递区间值
   }
   //把区间[L,R]开根号
   static void update(int p, int pl, int pr, int L, int R){ //区间修改:把[L, R]内每个元素取根
       if(ma[p] <= 1) return; //区间[L,R]最大值不超过1,说明整个区间均为1或者0
       if(pl == pr){ //叶子结点
           ma[p] = (int) Math.sqrt(ma[p]);
           sum[p] = ma[p];
           return;
       }
       int mid = (pl + pr) >> 1;
       if(L <= mid)  update(p << 1, pl, mid, L, R);
       if(R > mid)   update(p << 1 | 1, mid + 1, pr, L, R);
       push_up(p);
   }
   //查询区间[L,R]的和
   static long query(int p, int pl, int pr, int L, int R){
       if(L <= pl && pr <= R) return sum[p];
       int mid = (pl + pr) >> 1;
       long res = 0;
       if(L <= mid) res += query(p << 1, pl, mid, L, R);
       if(R > mid)  res += query(p << 1 | 1, mid + 1, pr, L, R);
       return res;
   }
}

Python代码

import sys
input = sys.stdin.readline           #加这句后读入会快些
n = int(input())
a = [0] * (n + 1)
a[1:] = list(map(int, input().split()))
ma = [0] * (n << 2)
sum = [0] * (n << 2)

def push_up(p):
    ma[p] = max(ma[p << 1], ma[p << 1 | 1]) #区间最值
    sum[p] = sum[p << 1] + sum[p << 1 | 1] #区间和

def build(p, pl, pr):
    if pl == pr: #最底层的叶子,赋值
        ma[p] = sum[p] = a[pl]
        return
    mid = (pl + pr) >> 1 #分治:折半
    build(p << 1, pl, mid) #左儿子
    build(p << 1 | 1, mid + 1, pr) #右儿子
    push_up(p) #从下往上传递区间值

#把区间[L,R]开根号
def update(p, pl, pr, L, R): #区间修改:把[L, R]内每个元素取根
    if ma[p] <= 1: return       #区间[L,R]最大值不超过1,说明整个区间均为1或者0        
    if pl == pr: #叶子结点
        ma[p] = int(ma[p] ** 0.5)
        sum[p] = ma[p]
        return
    mid = (pl + pr) >> 1
    if L <= mid:  update(p << 1, pl, mid, L, R)
    if R > mid:   update(p << 1 | 1, mid + 1, pr, L, R)
    push_up(p)

#查询区间[L,R]的和
def query(p, pl, pr, L, R):
    if L <= pl and pr <= R:  return sum[p]
    mid = (pl + pr) >> 1
    res = 0
    if L <= mid:   res += query(p << 1, pl, mid, L, R)
    if R > mid:    res += query(p << 1 | 1, mid + 1, pr, L, R)
    return res

build(1, 1, n)
m = int(input())
while m > 0:
    m -= 1
    op, L, R = map(int, input().split())
    if op == 1: print(query(1, 1, n, L, R))
    else:       update(1, 1, n, L, R)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗勇军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值