BZOJ 3211 花神游历各国 树状数组(线段树)+优化

题意:给你一段区间,然后每个点的初始值都告诉你,现有两种操作,一种是给你一个小区间的左右端点,之后把这个区间内的所有值都开根号,另一种就是区间求值。

方法:树状数组维护求和,巧妙开根号。(线段树)

解析:这道是某次考试考的题- -。当时也没想到快的开根号方法,暴力开根号好像70分吧。

首先要明确一个事情:被开根号的数最大是 109 ,而 109 开几次会开到1呢?用计算器算一下发现5次就将这个最大的数开到1了,而1开根号怎么开都是1,所以如果再对其进行开根号操作,仅仅是无用的浪费时间了。所以怎么维护这种问题呢?我们采用一个数组 fa[i] 代表 >=i 的第一个不为1的数的编号这样的话就能为我们节省很多的时间了。

具体怎么操作呢?

for(int i=find(l);i<=r;i=find(i+1))

用如上的for循环进行开根号,如果当前已开到1以下了,需要把他的fa数组进行更改,即在for循环里填上一句话:

if(p[i]<=1)fa[i]=find(i+1);

后记:这种思想在很多题都有过应用,这种优化的方式也是一种需要牢记的方式。

代码

#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std ;
typedef long long ll ;
ll c[100001] ;
ll p[100001] ;
ll fa[100001] ;
int n ;
ll lowbit(ll x)
{
    return x & (-x) ;
}
ll getsum(ll x)
{
    ll sum = 0 ;
    while(x>0)
    {
        sum += c[x] ;
        x -= lowbit(x) ;
    }
    return sum ;
}
void updata(int x , ll t)
{
    while(x <= n)
    {
        c[x] += t ;
        x += lowbit(x) ;
    }
}
int find(int x)
{
    if(fa[x] == x) return x ;
    else
    {
        fa[x] = find(fa[x]) ;
        return fa[x] ;
    }
}
int main()
{
    scanf("%d" , &n) ;
    for(int i = 1 ; i <= n ; i++)
    {
        scanf("%d" , &p[i]) ;
        updata(i , p[i]) ;
        fa[i] = i ;
    }
    fa[n+1] = n+1 ;
    int q ;
    scanf("%d" , &q) ;
    for(int i = 1 ; i <= q ; i++)
    {
        int x , l , r; 
        scanf("%d%d%d" , &x , &l , &r) ;
        if(x==1)
        {
            printf("%lld\n" , getsum(r) - getsum(l-1)) ;
        }else
        {
            for(int i = find(l) ; i<=r ; i = find(i+1))
            {
                int t = (int)(sqrt(p[i])) ;
                updata(i , t - p[i]) ;
                p[i] = t ;
                if(p[i] <= 1) fa[i] = find(i+1) ;
            }
        }
    }
}

你可能感兴趣的:(BZOJ 3211 花神游历各国 树状数组(线段树)+优化)