zoukankan      html  css  js  c++  java
  • 学习笔记:权值线段树

    虽然题解很多,也有权值线段树,但我的和他们似乎不尽相同,跑的也挺快。
    所谓权值线段树,就是用线段树来存储权值。
    那什么是权值呢?似乎小学初中学统计的时候了解到,他是描述数在数据中比例大小的量,这里用作此数出现的次数

    建树

    做法显然
    我们用(cnt_i)表示第(i)个数出现的次数,那么可以这样:

    void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum;}
    void build(int k,int l,int r)
    {
    	a[k].l=l,a[k].r=r;
    	int mid=(l+r)>>1;
    	if(l==r)
    	{
    		a[k].sum=cnt[l];//赋值
    		return;
    	}
    	build(k<<1,l,mid),build(k<<1|1,mid+1,r);//向下递归
    	update(k);//动态更新点区间和
    }
    

    之后

    你是不是还在疑问这是在回答什么问题?
    对这就是询问所有数中第(x)小值。

    分析

    如何运用权值线段树呢?
    很简单,我们每次查询下左儿子的权值和(sum),如果(xgeqslant sum),就向右儿子递归询问第(x-sum)小,否则向左儿子递归找第(x)值就好了。
    有没有发现纰漏?
    这个数组必须有单调性。
    那就排序啊......
    反正排序是(O(nlog n))的,线段树也是(O(nlog n))的,不会成为瓶颈。
    这里先不给代码了,下面再说。

    其他的东西

    我们想到线段树是单调数组,而使用是非常不便。
    这就需要一种给力的映射关系
    其实就是离散化了
    这里就有代码了:

    struct Node
    {
    	int val,id;
    }t[MAXN];
    bool cmp(Node n,Node m)
    {
    	return n.val<m.val;
    } 
    int b[MAXN],num[MAXN],cnt[MAXN],c=1;
    void hash(int n)
    {
    	sort(t+1,t+n+1,cmp);//结构体排序
    	for(int i=1;i<=n;i++)
    	{
    		if(num[c]!=t[i].val) b[t[i].id]=++c,num[c]=t[i].val;//如果和上一个不同
    		else b[t[i].id]=c;
    		cnt[c]++;
    	}
    }
    

    慢慢来,比较绕。
    (b[])是原数组到离散化数组的映射,而(num[])反之。
    举个栗子:
    数据:(5,12,-6,7,5)中,
    (b[1](t[1].val=5)=2,b[2](t[2].val=12)=4);
    (ps):这里给出的下标是未排序的。
    (num[1]=-6,num[3]=7);
    (cnt[2]=2(num[2]=num[3]=5))

    回到上题

    这样就可以求第(x)小值了。
    用上面的思想解决:

    int query(int k,int x)
    {
    	if(a[k].l==a[k].r) return num[a[k].l];//找到了
    	else if(a[k<<1].sum>=x) return query(k<<1,x);//在左儿子中
    	else return query(k<<1|1,x-a[k<<1].sum); //在右儿子中,记得减一下
    }
    

    题解

    你基本已经学会了权值线段树了,现在来看这个题:
    题目链接:P1801 黑匣子
    考虑权值线段树,加点并不好实现,不如强制离线,倒着试试。
    删除一个数就十分简单了,我们只需用原数到离散化数组的映射关系,将此数对应的(cnt--)就好了,把答案存一下,逆序输出来即可。
    下面是简单的单点修改:

    void modify(int k,int x,int y)//现在在k点,目标是将x号搞成y
    {
    	int mid=(a[k].l+a[k].r)>>1;
    	if(a[k].l==a[k].r)//找到目标
    	{
    		a[k].sum=y;
    		return;
    	}
    	else if(x<=mid) modify(k<<1,x,y);
    	else modify(k<<1|1,x,y);
    	update(k);//仍然记得更新
    }
    

    总的说,这样的复杂度是(O((n+m)log n))的,可以通过此题。
    下面放上(AC)代码:

    (Code):

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    using namespace std;
    const int MAXN=2e5+5;
    struct Node
    {
    	int val,id;
    }t[MAXN];
    bool cmp(Node n,Node m)
    {
    	return n.val<m.val;
    } 
    int b[MAXN],num[MAXN],cnt[MAXN],c=1;//num是hash数组到原数组的映射。
    /*num[2]=12,指原数组第二大的值为12*/ 
    void hash(int n)
    {
    	sort(t+1,t+n+1,cmp);
    	for(int i=1;i<=n;i++)
    	{
    		if(num[c]!=t[i].val) b[t[i].id]=++c,num[c]=t[i].val;
    		else b[t[i].id]=c;
    		cnt[c]++;
    	}
    }
    struct node
    {
    	int l,r,sum,val;
    	node()
    	{
    		l=r=sum=0;
    	}
    }a[MAXN<<2];
    void update(int k){a[k].sum=a[k<<1].sum+a[k<<1|1].sum;}
    void build(int k,int l,int r)
    {
    	a[k].l=l,a[k].r=r;
    	int mid=(l+r)>>1;
    	if(l==r)
    	{
    		a[k].sum=cnt[l];
    		return;
    	}
    	build(k<<1,l,mid),build(k<<1|1,mid+1,r);
    	update(k);
    }
    int query(int k,int x)
    {
    	if(a[k].l==a[k].r) return num[a[k].l];
    	else if(a[k<<1].sum>=x) return query(k<<1,x);
    	else return query(k<<1|1,x-a[k<<1].sum); 
    }
    void modify(int k,int x,int y)
    {
    	int mid=(a[k].l+a[k].r)>>1;
    	if(a[k].l==a[k].r)
    	{
    		a[k].sum=y;
    		return;
    	}
    	else if(x<=mid) modify(k<<1,x,y);
    	else modify(k<<1|1,x,y);
    	update(k);
    }
    int n,m,l[MAXN],now,ans[MAXN];
    int main()
    {
    	scanf("%d%d",&n,&m);
    	now=m;
    	for(int i=1;i<=n;i++) scanf("%d",&t[i].val),t[i].id=i;
    	for(int i=1;i<=m;i++) scanf("%d",&l[i]);
    	hash(n);
    	build(1,1,c);
    	for(int i=n;i>=1;i--)
    	{
    		while(i==l[now]) ans[now]=query(1,now),now--;//此时有询问,查询第now小值,并更新now
    		,modify(1,b[i],--cnt[b[i]]);//将数对应的点权值减一
    	}
    	for(int i=1;i<=m;i++) printf("%d
    ",ans[i]);//将存储的答案输出
    	return 0; 
    }
    

    这个版本的权值线段树会跑(900;ms),翻翻记录,还挺快呢。

  • 相关阅读:
    数据库
    linux
    linux
    python_函数进阶1
    python_函数初识
    python_文件操作
    python_基础数据类型补充
    python 小数据池,代码块总览
    python_基础数据类型二
    python_基础数据类型一
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12354465.html
Copyright © 2011-2022 走看看