《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 给定长度为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编码,代码少了很多。
【重点】 基本的线段树模板。
#include
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;
}
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;
}
}
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)