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) ;
            }
        }
    }
}
时间: 2024-08-02 11:02:50

BZOJ 3211 花神游历各国 树状数组(线段树)+优化的相关文章

[BZOJ 3211]花神游历各国(并查集+树状数组)

Description Solution 树状数组单点修改区间查询 我们知道一个数n最多修改loglogn次就会变为1 并查集维护每个数右边第一个不为1的位置 #include<cstdio> #include<iostream> #include<cstdlib> #include<cstring> #include<cmath> #define MAXN 100005 using namespace std; typedef long lon

BZOJ 3211 花神游历各国 (树状数组+并查集)

题解:首先,单点修改求区间和可以用树状数组实现,因为开平方很耗时间,所以在这个方面可以优化,我们知道,开平方开几次之后数字就会等于1 ,所以,用数组记录下一个应该开的数,每次直接跳到下一个不是1的数字进行开平方,至于这个数组,可以用并查集维护. #include <cstdio> #include <cmath> #include <iostream> using namespace std; typedef long long LL; LL c[100005]; in

BZOJ 3211 花神游历各国 线段树题解

BZOJ 3211 花神游历各国 线段树题解 3211: 花神游历各国 Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 2551  Solved: 946[Submit][Status][Discuss] Description Input Output 每次x=1时,每行一个整数,表示这次旅行的开心度 Sample Input 4 1 100 5 5 5 1 1 2 2 1 2 1 1 2 2 2 3 1 1 4 Sample Output 101

hdu 1166 树状数组 线段树

敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 51177    Accepted Submission(s): 21427 Problem Description C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了.A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务

hdu1394(枚举/树状数组/线段树单点更新&amp;区间求和)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1394 题意:给出一个循环数组,求其逆序对最少为多少: 思路:对于逆序对: 交换两个相邻数,逆序数 +1 或 -1, 交换两个不相邻数 a, b, 逆序数 += 两者间大于 a 的个数 - 两者间小于 a 的个数: 所以只要求出初始时的逆序对数,就可以推出其余情况时的逆序对数.对于求初始逆序对数,这里 n 只有 5e3,可以直接暴力 / 树状数组 / 线段树 / 归并排序: 代码: 1.直接暴力 1

HDU 1394 Minimum Inversion Number 树状数组&amp;&amp;线段树

题目给了你一串序列,然后每次 把最后一个数提到最前面来,直到原来的第一个数到了最后一个,每次操作都会产生一个新的序列,这个序列具有一个逆序数的值,问最小的你逆序数的值为多少 逆序数么 最好想到的是树状数组,敲了一把很快,注意把握把最后一个数提上来对逆序数的影响即可, #include<iostream> #include<cstdio> #include<list> #include<algorithm> #include<cstring> #i

HDU 1166 敌兵布阵 (树状数组&#183;线段树)

题意  中文 动态区间和问题   只会更新点  最基础的树状数组 线段树的应用 树状数组代码 #include <bits/stdc++.h> using namespace std; const int N = 50005; int c[N], n, m; void add(int p, int x) { while(p <= n) c[p] += x, p += p & -p; } int getSum(int p) { int ret = 0; while(p > 0

BZOJ 3211: 花神游历各国( 线段树 )

线段树...区间开方...明显是要处理到叶节点的 之前在CF做过道区间取模...差不多, 只有开方, 那么每个数开方次数也是有限的(0,1时就会停止), 最大的数10^9开方10+次也就不会动了.那么我们线段树多记个max就可以少掉很多不必要的操作 -------------------------------------------------------------------------------------------- #include<cstdio> #include<cs

BZOJ 3211 花神游历各国 树状数组+并查集

题目大意:花神对每一个国家有一个喜爱程度,有的时候他会对连续的一段国家进行访问,求他的喜爱程度的和:有的时候他会对连续的一段国家产生厌恶,喜爱程度变成sqrt(x)下取整. 思路:乍一看好像是RMQ问题,用线段树就可以水过,但是开根号的标记怎么下传?这是一个严重的问题,所以我们要换一个思路. 注意到开根号有一个有趣的性质:sqrt(1) = 1,sqrt(0) = 0,而且所有的数字经过有限次的开根号运算都会变成1.这个性质就很好了.我们对每一个点暴力开根号,然后当这个店的点权变成1的时候就打一