函数式线段树的个人理解

这几天一直在搞这个东西,今天总算搞懂了,函数式线段树是一种解决离线算法的数据结构,我是这样理解的,它将所有数据离散化,再对每一个节点 N 建一颗(1,N)的线段树,这是它的思路,当然如果真正的去建这么多线段树,内存肯定爆了,所以这个就是函数式线段树的高级的地方,它从分利用前缀和的思想,后一颗树和前一棵树分享了一半的节点,什么意思呢,若现在我们得到了T[N],也就是(1,N)这些节点的线段树,我们要建(1,N+1)这颗线段树T[N+1],这样我们只需要在T[N]的基础上加入a[N+1]这个节点,如果a[N+1]被放入T[N+1]的左子树,那么T[N+1]将和T[N]公用右子树,同理,若a[N+1]被放入右子树,T[N+1]将和T[N]公用左子树,这样就能减少太多内存的消耗了。而建好这颗树之后我们就可以用它的性质去解决一些题目,特别是区间第k大数,可同属处理动态和静态的问题,对于静态的若要查询(L,R,K),只需看看R的左子树和L-1的左子树之差,若大于K,继续向他们的左子树查找第K大值,若小于K,则要向他们的右子树查找K-他们的差,这样直到到叶子节点,就是我们要的答案了。而对于动态的,我们可以用树状数组来套这颗函数式线段树,即树状数组的每个节点都是一颗线段树,这样来处理,下面给出一个静态的程序

POJ 2104

#include<map>
#include<set>
#include<stack>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<string>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define  inf 0x0f0f0f0f

using namespace std;

const double pi=acos(-1.0);
const double eps=1e-8;
typedef pair<int,int>pii;

const int maxn=200000+10;
const int N=maxn*30;

int a[maxn],b[maxn],Ls[N],Rs[N],T[maxn],sum[N],n,m,tot;

void init()
{
     sort(b+1,b+n+1);
     m=unique(b+1,b+n+1)-b-1;
     for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+m+1,a[i])-b;
}

int build(int x,int y)
{
     int root=tot++;
     sum[root]=0;
     if (x==y) return root;
     int mid=x+(y-x)/2;
     Ls[root]=build(x,mid);
     Rs[root]=build(mid+1,y);
}

int insert(int root,int x,int v)
{
     int newroot=tot++,temp=newroot;
     sum[newroot]=sum[root]+v;
     int L=1,R=m;
     while(L<R)
     {
          int mid=L+(R-L)/2;
          if (x<=mid)
          {
               Ls[newroot]=tot++; Rs[newroot]=Rs[root]; newroot=Ls[newroot]; root=Ls[root]; R=mid;
          }
          else
          {
               Ls[newroot]=Ls[root]; Rs[newroot]=tot++; newroot=Rs[newroot]; root=Rs[root]; L=mid+1;
          }
          sum[newroot]=sum[root]+v;
     }
     return temp;
}

int get_K_num(int Lroot,int Rroot,int k)
{
     int L=1,R=m;
     while(L<R)
     {
          int mid=L+(R-L)/2;
          int t=sum[Ls[Rroot]]-sum[Ls[Lroot]];
          if (t>=k)
          {
               R=mid; Rroot=Ls[Rroot]; Lroot=Ls[Lroot];
          }
          else
          {
               L=mid+1; Rroot=Rs[Rroot]; Lroot=Rs[Lroot]; k-=t;
          }
     }
     return L;
}

int main()
{
     int q,x,y,k;
     while(scanf("%d%d",&n,&q)!=EOF)
     {
          tot=0;
          for (int i=1;i<=n;i++)
          {
               scanf("%d",&a[i]); b[i]=a[i];
          }
          init();
          T[0]=build(1,m);
          for (int i=1;i<=n;i++) T[i]=insert(T[i-1],a[i],1);
          while(q--)
          {
               scanf("%d%d%d",&x,&y,&k);
               printf("%d\n",b[get_K_num(T[x-1],T[y],k)]);
          }
     }
     return 0;
}

作者 chensunrise

函数式线段树的个人理解

时间: 2024-10-12 12:45:16

函数式线段树的个人理解的相关文章

Codeforces538F A Heap of Heaps(函数式线段树)

题意:给你一个数组a[n],对于数组每次建立一个完全k叉树,对于每个节点,如果父节点的值比这个节点的值大,那么就是一个违规点,统计出1~n-1完全叉树下的违规点的各自的个数. 一个直觉的思想就是暴力,因为完全k叉树当k很大的时候,其实层数是特别小的,所以感觉暴力是可以的.注意到一个完全k叉树下v节点的儿子的公式是: k*(v-1)+2...kv+1,相应的父节点的公式是 (v+k-2)/k.儿子的编号是连续的,如果我们可以对每个节点快速的求出连续编号的节点有多少个数比它小我们就可以快速的更新答案

主席树(函数式线段树)学习小结(附手绘讲解图片)

主席树是一种离线数据结构,是由很多棵线段树组成的. 第i棵线段树存的是前i个数的信息: 每一个线段存数字的出现次数(因此建树之前要离散化). 那么n棵线段树不会MLE吗? 当然会了! 但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 具体建树方法建下图: 序列为 1 3 4 2 那么如果要询问i-j之间数字出现的次数怎么办呢? 因为每一棵线段树的区间都是相同的,所以要求l-r之间的数字的出现次数只要用前r位出现的次数减去前l-1

HDU-4866-Shooting(函数式线段树)

Problem Description In the shooting game, the player can choose to stand in the position of [1, X] to shoot, you can shoot all the nearest K targets. The value of K may be different on different shootings. There are N targets to shoot, each target oc

POJ-2104-K-th Number(函数式线段树)

Description You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array

主席树/函数式线段树/可持久化线段树

什么是主席树 可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本.同时充分利用它们之间的共同数据来减少时间和空间消耗. 因此可持久化线段树也叫函数式线段树又叫主席树. 可持久化数据结构 在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本.这样的集合称为是可持久的. 实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间. 考虑一个持久集合S. 如图所示,对集合的

hdu 2665 可持久化线段树求区间第K大值(函数式线段树||主席树)

http://acm.hdu.edu.cn/showproblem.php?pid=2665 Problem Description Give you a sequence and ask you the kth big number of a inteval. Input The first line is the number of the test cases. For each test case, the first line contain two integer n and m (

【POJ2104】K-th Number 主席树?函数式线段树?可持久化线段树?……反正是其中一个

题意:区间静态第K大. 题解: 可持久化线段树. 可持久化线段树: 基本思想:我们维护插入每个节点后的线段树. 朴素写法(MLE+TLE)我们对于每次插入,都复制一棵线段树而后插入,这样保证了"可持久化". 但是显然,时间复杂度和空间复杂度都是n^2的.233. 所以有了优化写法:我们发现每次的插入只有logn个节点被改变,所以只需要这些点新建,其它点都指向原来版本的节点就好了. 空间复杂度nlogn. 然后这道题是区间第K大,对于区间[a,b],插入到b时的线段树,节点的size-(

关于线段树的初步理解

POJ 3264 根据题目意思就是给定一段序列,然后给出几个区间,要求出该段区间中最大值与最小值之差. 首先我想到的是用数组存储这一段序列,然后每次根据区间的左右边界来遍历这个段序列然后找到最大值和最小值,显然这样的方法是最容易想到的,但是可想而知这样的方法会耗费很多的时间,时间复杂度太大. 然后,我进行了第二种思考,就是利用二维数组来存储左边界到右边界的最大值: (1)如果l==r,那么显然当前区间的最大值和最小值就是l这个位置的数本身 (2)max1[i][j]=max{max[i][j-1

3641-货车运输-函数式线段树-分类讨论-环套树

3641: 货车运输 Time Limit: 20 Sec  Memory Limit: 256 MBSec  Special JudgeSubmit: 64  Solved: 27[Submit][Status][Discuss] Description saffah所在的国家一共有N个城市,通过N条双向道路连接,使得城市之间两两可以到达.第i条道路连接了A[i]与B[i],其长度为L[i] km.这些道路分为M个等级.第i条道路的等级为X[i].在第j级道路上行驶,你的限速是V[j] km/