上帝造题的七分钟 2 / 花神游历各国

GitHub跳转原题关系图返回列表

线段树维护区间和与最值,利用平方根快速收敛剪枝区间开方。

OJ: luogu

题目 ID: P4145

难度:提高

标签:线段树区间开方剪枝python

日期: 2026-07-16 23:59

题意

对区间每个数做一次向下取整平方根,或查询区间和。

思路

数值为 1 时再开方不会改变。节点维护区间和、最小值、最大值:若整段 min == max,可以直接把整段赋成 isqrt(value);若 max <= 1,直接剪枝。否则递归到相交子区间。平方根操作会让大数迅速下降,均摊访问量可控。

Python 知识

  • math.isqrt 是整数平方根,避免浮点精度问题。
  • 赋值懒标记用 0 表示“没有标记”,因为题目中的数始终为正。
  • max <= 1 的提前 return 是这类势能下降操作的关键优化。

代码

python
import sys
from array import array
from math import isqrt


sys.setrecursionlimit(1_000_000)
input = sys.stdin.buffer.readline
n = int(input())
values = array("q", map(int, input().split()))
operations = int(input())
total = array("q", [0]) * (4 * n)
minimum = array("q", [0]) * (4 * n)
maximum = array("q", [0]) * (4 * n)
assigned = array("q", [0]) * (4 * n)


def apply(node, value, length):
    total[node] = value * length
    minimum[node] = maximum[node] = value
    assigned[node] = value


def build(node, left, right):
    if left == right:
        total[node] = minimum[node] = maximum[node] = values[left - 1]
        return
    middle = (left + right) // 2
    build(node * 2, left, middle)
    build(node * 2 + 1, middle + 1, right)
    pull(node)


def pull(node):
    left, right = node * 2, node * 2 + 1
    total[node] = total[left] + total[right]
    minimum[node] = min(minimum[left], minimum[right])
    maximum[node] = max(maximum[left], maximum[right])


def push(node, left, right):
    if assigned[node] and left != right:
        middle = (left + right) // 2
        apply(node * 2, assigned[node], middle - left + 1)
        apply(node * 2 + 1, assigned[node], right - middle)
        assigned[node] = 0


def update(node, left, right, query_left, query_right):
    if query_right < left or right < query_left or maximum[node] <= 1:
        return
    if query_left <= left and right <= query_right and minimum[node] == maximum[node]:
        apply(node, isqrt(maximum[node]), right - left + 1)
        return
    push(node, left, right)
    middle = (left + right) // 2
    if query_left <= middle:
        update(node * 2, left, middle, query_left, query_right)
    if middle < query_right:
        update(node * 2 + 1, middle + 1, right, query_left, query_right)
    pull(node)


def query(node, left, right, query_left, query_right):
    if query_left <= left and right <= query_right:
        return total[node]
    push(node, left, right)
    middle = (left + right) // 2
    answer = 0
    if query_left <= middle:
        answer += query(node * 2, left, middle, query_left, query_right)
    if middle < query_right:
        answer += query(node * 2 + 1, middle + 1, right, query_left, query_right)
    return answer


build(1, 1, n)
answers = []
for _ in range(operations):
    kind, left, right = map(int, input().split())
    if left > right:
        left, right = right, left
    if kind:
        answers.append(str(query(1, 1, n, left, right)))
    else:
        update(1, 1, n, left, right)
print("\n".join(answers))

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-16 23:46
 * update_at: 2026-07-16 23:46
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

建树 O(n);单次操作为线段树访问并带平方根收敛剪枝,空间 O(n)

总结

当区间修改会快速降低数值时,维护最值并在“整段相同”时批量处理,通常比强行维护复杂懒标记更简单。