• POJ2104 K-th number 函数式线段树


    很久没打代码了,不知道为什么,昨天考岭南文化之前突然开始思考起这个问题来,这个问题据说有很多种方法,划分树什么的,不过对于我现在这种水平还是用熟悉的线段树做比较好。这到题今年8月份的时候曾经做过,那个时候是作为对函数式线段树的一个基础题来做的,那个时候不懂,看着别人的代码对拍然后摸索下来,所以到了昨天我就已经彻底忘了,因为代码不是自己写的。昨天想了很久,终于参透了它的精髓。

    首先对于给出的a[1]~a[n]这么多个数离散化,然后建立一个线段树,线段树中的结点对应的区间[L,R]表示的是在[L,R](离散化后的值)中有多少个数。

    举个例子,a={1,3,2,6,4,7}  那么假如我对这整个数组建线段树,[3,6]的sum应该为2,因为[3,6]里只有3和5在数组里。

    函数式的思想是,假如我建出来从a[1]~a[i]对应的线段树T[i],那么当我要去求数组中的某一段[x,y]在[L,R]里有多少个的时候,实际上就是T[y].[L,R]的和-T[x-1].[L,R]的和,这样我们就可以知道,在数组的a[x]~a[y]里在范围在L-R中的数有多少个。这样的话我们可以二分出第kth值。

    即从0~size,如果0~mid里已经有超过k个了,说明第k大在左边,否则第k大在右边,这样查找就可以了。这次我终于写出了一个自己的二分。以前二分都是lower_bound水过,如果要用到的话,我会在l+1=r的时候特判,特别猥琐,看了一篇关于二分的总结才知道,二分的写法有分左闭右闭和左开右开的,不过这些都要慢慢领悟了。

    再谈谈怎么建线段树,假如我们已经建了线段树T[i],那么当我们要建线段树T[i+1]的时候,如果再建一次空间会非常大,所以要充分利用T[i]的信息,实际上建立T[i+1]的时候只比T[i]多了一个值,所以我们只需要延着路径重建这些新的结点就好,另外一边的只需要用回历史版本的就好。延着线段树往下会修改到logn个结点,所以建立T[0]时需要开4*n,再加上后来每个一个都要logn个结点,所以总的结点数是(4+logn)*n.  logn=16.多,这也就是为什么我们的结点数要开到20倍,所以这种树对空间的消耗是蛮大的。

    想想之前线段树都要套模板,现在可以自己敲出来,这一年里还是有进步的。我还是慢慢努力练习练习吧~

    贴一记代码,1700ms,非常慢,权当学习- -0

    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<string>
    #include<cstdio>
    #include<cmath>
    #define maxn 100000
    using namespace std;
    
    struct Node
    {
    	int l,r,sum;
    	Node *lc,*rc;
    }N[20*maxn];
    int top;
    
    Node *root[100050];
    int n,m;
    int a[maxn+20];
    int b[maxn+20];
    int bsize;
    
    Node *build(int L,int R)
    {
    	if(L==R){
    		Node *ret=&N[top++];
    		ret->l=L;ret->r=R;ret->sum=0;
    		ret->lc=NULL;ret->rc=NULL;
    		return ret;
    	}
    	else{
    		Node* ret=&N[top++];
    		int M=(L+R)>>1;
    		ret->l=L;ret->r=R;
    		ret->lc=build(L,M);ret->rc=build(M+1,R);
    		ret->sum=ret->lc->sum+ret->rc->sum;
    		return ret;
    	}
    }
    
    Node *build(int L,int R,int k,Node *p)  // 对key值k,和历史版本的p重建树
    {
    	if(L==R){
    		Node *ret=&N[top++];
    		ret->l=L;ret->r=R;ret->sum=p->sum+1;
    		ret->lc=NULL;ret->rc=NULL;
    		return ret;
    	}
    	else{
    		int M=(L+R)>>1;
    		Node *ret=&N[top++];
    		ret->l=L;ret->r=R;
    		if(k<=M){   
    			ret->lc=build(L,M,k,p->lc); // k值在原树的左边的时候要重建左子树
    			ret->rc=p->rc;  // 右子树不变,用历史版本
    			ret->sum=ret->lc->sum+ret->rc->sum; // 更新和值
    			return ret;
    		}
    		else{
    			ret->rc=build(M+1,R,k,p->rc); // k值在原树的右边的时候要重建右子树
    			ret->lc=p->lc; // 左子树不变,用历史版本
    			ret->sum=ret->lc->sum+ret->rc->sum; // 更新和值
    			return ret;
    		}
    	}
    }
    
    int query(Node *p,int L,int R) // 对结点p的[L,R]区间求和值
    {
    	if(p->l==L&&p->r==R){
    		return p->sum;
    	}
    	else{
    		int M=(p->l+p->r)>>1;
    		if(R<=M) return query(p->lc,L,R);
    		else if(L>M) return query(p->rc,L,R);
    		else return query(p->lc,L,M)+query(p->rc,M+1,R);
    	}
    }
    
    int search(int x,int y,int k) //找从x到y上的区间第k大
    {
    	int l=0,r=bsize;int mid,num;
    	while(l<r){
    		mid=(l+r)>>1;
    		num=query(root[y],l,mid)-query(root[x-1],l,mid); // 算出<=mid的有多少个,利用区间可以减的性质
    		if(k<=num) r=mid;
    		else{
    			k-=num;l=mid+1;
    		}
    	}
    	return l;
    }
    
    int main()
    {
    	while(cin>>n>>m)
    	{
    		for(int i=0;i<n;++i){
    			scanf("%d",&a[i]);
    			b[i]=a[i];
    		}
    		//离散化的过程
    		sort(b,b+n);bsize=unique(b,b+n)-b;
    		for(int i=0;i<n;++i){
    			a[i]=lower_bound(b,b+bsize,a[i])-b;
    		}
    		top=0;
    		root[0]=build(0,bsize);
    		for(int i=1;i<=n;++i){
    			root[i]=build(0,bsize,a[i-1],root[i-1]); // 根据a[i-1]的值,以及上一个版本的树建立新的子树
    		}
    		int li,ri,ki;
    		for(int i=0;i<m;++i){
    			scanf("%d%d%d",&li,&ri,&ki);
    			printf("%d
    ",b[search(li,ri,ki)]);
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    领导满意的高逼格报告,可视化吊打Excel,仅用5步就能教会你
    TTL
    s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0)
    VSCode的配置和插件同步到多台电脑
    C 语言编程 — 高级数据类型 — void 类型
    C 语言编程 — 高级数据类型 — 字符串
  • 原文地址:https://www.cnblogs.com/chanme/p/3481917.html
Copyright © 2020-2023  润新知