您的位置:首页 > 产品设计 > UI/UE

hdu5828 Rikka with Sequence (线段树:区间开根+区间求和+区间加减)

2017-08-04 11:33 435 查看

题意:

对一个n元素序列进行三种操作:
1 l r x : 对a[l,r]之间所有元素都加上x
2 l r: 对a[l,r]之间所有元素开根号
3 l r: 输出a[l,r]之间的元素和

(1<=n,m<=1e5 , 1<=A[i],x<=1e5)


分析:

这道题难点就在“区间开根”这一操作。因为如果像正常的区间更新一样,用懒惰标记,我们会发现无法快速更新区间信息,也就是说无法快速得到它询问的区间和。

所以,类似“区间取模”,需要分析开根操作的特点。我们发现,即使是两个差很大的数,都可以在很少次数的开根计算后趋近于相等,最后都变成1。

当一个区间内的数都相等会怎样呢?

这时区间开根操作就变为了区间减法,开根就相当于所有的数都减了一个数。

当一个区间的极差是1会怎样呢?开根后会有两种情况:

极差仍然为1。那开根就相当于都减去一个数。

极差变为0。下次开根就是区间减法。(此处也可看作是区间覆盖)

若区间极差大于1,那就递归暴力访问更新。直至变成上面的情况。

注意点:

laz标记用来标记“区间加减”的时候,初始值一定是 0 而不是 -1,-1 的话在第一次laz[rt] += x 时会出错。

试了一次输入外挂, 发现确实会快300ms

代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MAXN = 1e5 + 5;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
#define lson rt*2,l,(l+r)/2
#define rson rt*2+1,(l+r)/2+1,r
ll mx[MAXN << 2], mi[MAXN << 2], sum[MAXN << 2], laz[MAXN << 2];
int n;
void pushup(int rt) {
sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]);
mi[rt] = min(mi[rt << 1], mi[rt << 1 | 1]);
}
void pushdown(int rt, int l, int r) {
if (laz[rt] != 0) {
int mid = (l + r) / 2;
laz[rt << 1] += laz[rt];    laz[rt << 1 | 1] += laz[rt];
sum[rt << 1] += laz[rt] * (mid - l + 1);
sum[rt << 1 | 1] += laz[rt] * (r - mid);
mx[rt << 1] += laz[rt]; mx[rt << 1 | 1] += laz[rt];
mi[rt << 1] += laz[rt]; mi[rt << 1 | 1] += laz[rt];
laz[rt] = 0;
}
return;
}
void build(int rt, int l, int r) {
if (l == r) {
scanf("%lld", &sum[rt]);
mx[rt] = mi[rt] = sum[rt];
return;
}
build(lson);
build(rson);
pushup(rt);
}
void update(int L,int R,int rt, int l, int r, ll x) {
if (L <= l && R >= r) {
laz[rt] += x;
sum[rt] += x * (r - l + 1);
mx[rt] += x;
mi[rt] += x;
return;
}
pushdown(rt, l, r);
if (L <= (l + r) / 2)   update(L,R,lson, x);
if (R > (l + r) / 2)    update(L,R,rson, x);
pushup(rt);
}
void sub(int rt, int l, int r, ll x) {
laz[rt] -= x;
sum[rt] -= (r - l + 1) * x;
mx[rt] -= x;
mi[rt] -= x;
}
void change(int L,int R,int rt, int l, int r) {
if (L <= l && R >= r) {
ll a = mx[rt], b = mi[rt];
if ((a - b) <= 1 && ((ll)sqrt(a) - (ll)sqrt(b)) == (a - b)) {
sub(rt, l, r, a - (ll)sqrt(a));
return;
}
}
pushdown(rt, l, r);
if (L <= (l + r) / 2)   change(L,R,lson);
if (R > (l + r) / 2)    change(L,R,rson);
pushup(rt);
}
ll query(int L,int R,int rt, int l, int r) {
if (L <= l && R >= r) {
return sum[rt];
}
pushdown(rt, l, r);
ll ans = 0;
if (L <= (l + r) / 2)   ans += query(L,R,lson);
if (R > (l + r) / 2)    ans += query(L,R,rson);
pushup(rt);
return ans;
}
int main() {
int T, m;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
build(1, 1, n);
ms(laz, 0);
while (m--) {
int op,L,R;
ll x;
scanf("%d%d%d", &op, &L, &R);
if (op == 1)    {scanf("%lld", &x); update(L,R,1, 1, n, x);}
else if (op == 2)   change(L,R,1, 1, n);
else    printf("%lld\n", query(L,R,1, 1, n));
}
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  hdu 线段树 区间开根