题目描述
You are given a sequence (A) of (N(N leq 100,000)) positive integers. There sum will be less than (10^{18}) . On this sequence you have to apply (M (M leq 100,000)) operations:
(A) For given (x),(y), for each elements between the (x-th) and the (y-th) ones (inclusively, counting from (1)), modify it to its positive square root (rounded down to the nearest integer).
(B) For given (x),(y), query the sum of all the elements between the (x-th) and the (y-th) ones (inclusively, counting from (1)) in the sequence.
输入输出格式
输入格式
Multiple test cases, please proceed them one by one. Input terminates by (EOF).
For each test case:
The first line contains an integer (N). The following line contains (N) integers, representing the sequence (A_{1}..A_{N}).
The third line contains an integer (M). The next (M) lines contain the operations in the form i x y
.
(i=0) denotes the modify operation, (i=1) denotes the query operation.
输出格式
For each test case:
Output the case number (counting from (1)) in the first line of output. Then for each query, print an integer as the problem required.
Print an blank line after each test case.
See the sample output for more details.
输入输出样例
输入样例#1
5
1 2 3 4 5
5
1 2 4
0 2 4
1 2 4
0 4 5
1 1 5
4
10 10 10 10
3
1 1 4
0 2 3
1 1 4
输出样例#1
Case #1:
9
4
6
Case #2:
40
26
题意翻译
「题意」: (n) 个数,和在(10^{18}) 范围内。
也就是(sum~a_i~leq~10^{18})
现在有「两种」操作
0 x y
把区间([x,y]) 内的每个数开方,下取整
1 x y
询问区间([x,y]) 的每个数的和
「格式」: 有多组数据,数据以(EOF)结束,对于每组数据,输出数据的序号,每组数据之后输出一个空行。
「注意」: 不保证给出的区间([x, y]) 有(x <= y) ,如果(x>y) 请交换(x) ,(y)。
题解
(SPOJ)的(GSS)系列第四题。
乍一看和这题一模一样,于是准备直接拿一样的代码提交。
猛然发现:数据范围差了那么远!
这题中(0 leq n leq 50000),(-2^{31} leq others)、(ans leq 2^{31} - 1)。
而本题中(n leq 100000),(sum~a_i~leq~10^{18})直接提交一样的代码肯定会(TLE)。
于是尝试线段树。
普通操作与线段树大同小异,只是需要注意一个点:一个数已经小于或等于1就不要再开方了!
为什么呢?因为大于(1)的数开方会越来越接近(1),而小于(1)的数开方也会越来越接近(1),又因为(sqrt{1} = 1),因此任何数经过开方操作都可以到(1)。
不难得出(AC)代码。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cctype>
using namespace std;
inline int gi()//快速读入
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar();}
return f * x;
}
const int maxn = 100000 + 5;
int n, m, Case;
long long tr[maxn << 2], a[maxn], sq[maxn << 2];//tr为区间和,a为数字序列,sq为区间最大值
inline void pushup(int p)//上传节点
{
tr[p] = tr[p << 1] + tr[(p << 1) | 1];//增加区间和
sq[p] = max(sq[p << 1], sq[(p << 1) | 1]);//计算最大值
}
void build(int s, int t, int p)//建树
{
if (s == t)//已经是叶子节点
{
tr[p] = sq[p] = a[s];//初始化节点信息
return;//直接返回
}
int mid = (s + t) >> 1;//计算中点
build(s, mid, p << 1);//递归左子树
build(mid + 1, t, (p << 1) | 1);//递归右子树
pushup(p);//上传当前节点
}
void modify(int l, int r, int s, int t, int p)//进行区间开方操作
{
if (s == t)//已经到了叶子节点
{
tr[p] = sqrt(tr[p]);//进行开方
sq[p] = tr[p];//区间最大值就是当前数
return;//返回
}
int mid = (s + t) >> 1;//计算中间值
if (l <= mid && sq[p << 1] > 1) //如果目标节点在左区间且左区间最大值大于1
modify(l, r, s, mid, p << 1);//就递归左子树寻找目标节点
if (r > mid && sq[(p << 1) | 1] > 1) //目标节点在右区间且右区间最大值大于1
modify(l, r, mid + 1, t, (p << 1) | 1);//递归右子树寻找目标节点
pushup(p);//上传当前节点
}
long long getans(int l, int r, int s, int t, int p)//寻找答案
{
if (l <= s && r >= t)//当前区间包含于目标区间
{
return tr[p];//直接返回当前区间信息
}
long long sum = 0;//要返回的和
int mid = (s + t) >> 1;//计算中间值
if (l <= mid)//如果左端点在中点左侧
{
sum = sum + getans(l, r, s, mid, p << 1);//加上左区间的答案
}
if (r > mid)//如果右端点在中点右侧
{
sum = sum + getans(l, r, mid + 1, t, (p << 1) | 1);//加上右区间的答案
}
return sum;//返回答案
}
int main()
{
while (~scanf("%d", &n))//多组数据
{
printf("Case #%d:
", ++Case);
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
memset(tr, 0, sizeof(tr));
memset(sq, 0, sizeof(sq));//多组数据要初始化
build(1, n, 1);//建树
m = gi();//输入询问个数
for (int i = 1; i <= m; i++)
{
int x = gi(), y = gi(), z = gi();
if (y > z) swap(y, z);//如果左端点大于右端点,就交换它们
if (x == 1)//是询问区间和
{
printf("%lld
", getans(y, z, 1, n, 1));//输出答案
}
else
{
modify(y, z, 1, n, 1);//否则就进行区间开方
}
}
puts("");//一定记得数据之间要空行
}
return 0;//结束
}