树状数组的奇妙应用,99%的人都不知道!

版权声明:本文为博主原创文章,转载请注明源网址blog.csdn.net/leo_h1104 https://blog.csdn.net/Leo_h1104/article/details/80749200

提到树状数组,大多数人的印象是只能求区间的前缀和。然而,树状数组还有很多其他的用法,用来替代线段树可有效降低空间复杂度和代码长度。
贴一份前缀和代码以供参考。

int c[maxn];
int n;//可能的下标最大值
int lowbit(int x)
{
    return x&(-x);
}
void add(int p,int v)//修改单点
{
    while(p<=n)
    {
        c[p]+=v;
        p+=lowbit(p);
    }
}
int prefix_sum(int p)//前缀和
{
    int sum=0;
    while(p)
    {
        sum+=c[p];
        p-=lowbit(p);
    }
    return sum;
}

维护后缀信息

只要把修改操作和查询操作的遍历顺序反过来,就可以存储后缀信息!
原理是c[i]现在存储从i往后数lowbit(i)个位置的信息

void add(int p,int v)
{
    while(p)
    {
        c[p]-=v;
        p+=lowbit(p);
    }
}
int suffix_sum(int p)
{
    int sum=0;
    while(p<=n)
    {
        sum+=c[p];
        p+=lowbit(p);
    }
    return sum;
}

这个实用性并没有想象中的大,因为所有后缀信息问题都可以转换成前缀问题。

维护最值

以维护最大值为例

void insert(int p,int v)
{
    while(p<=n)
    {
        c[p]=max(c[p],v);
        p+=lowbit(p);
    }
}
int prefix_max(int p)
{
    int mx=0;
    while(p)
    {
        mx=max(mx,c[p]);
        p-=lowbit(p);
    }
    return sum;
}

因为最值不满足区间可减性(已知(a,b)和(a,c)的答案不能得出(b,c)的答案),所以只能求前缀的最大值,是不能像线段树一样任意求出(l,r)的最值的。和线段树一样,不能修改。

查询第k大

分析c[i]的实际意义:从c[i]往前数lowbit(i)个的和。lowbit是二进制最后一个1代表的大小,因此我们可以按位讨论第k大数的位置,复杂度O(logn),并不是必须外层二分套里面的查前缀和

int base;
void init()
{
    base=1;
    while(base*2<=n)
        base<<=1;
}
void insert(int p)
{
    while(p<=n)
    {
        c[p]++;
        p+=lowbit(p);
    }
}
int kth(int k)
{
    int p=0;
    for(int step=base;step;step>>=1)
    {
        if(p+step<=n&&k>c[p+step])
        {
            p+=step;
            k-=c[p];
        }
    }
    return p+1;
}

这个只能求所有数据中的第k大,不如线段树灵活。权值线段树可以自定义L,R求L<x<R的第k大的数。而相比于静态的二分,它的优点是可以随时插入和删除元素。这有效填补了set功能中的一个空白。

阅读更多
换一批

一个50%的人都不知道多线程调度问题

06-03

我这里有两个类,实现了两个线程,代码程序见下面rnrnclass ThreadA rn /**rn * @param argsrn */rn /**rn * @param argsrn */rn public static void main(String[] args) rn Thread b = new Thread(new ThreadC());rn //ThreadC b = new ThreadC();rn b.start();rn rn System.out.println("b is start....");rn synchronized (b)// 括号里的b是什么意思,起什么作用?rn rn try rn //Thread.sleep(20);rn //b.setTotal();rn System.out.println("Waiting for b to complete...");rn b.wait();// 这一句是什么意思,究竟让谁wait?rn System.out.println("Completed.Now back to main thread");rn catch (InterruptedException e) rn rn rn rnrnrnrnpublic class ThreadC implements Runnable rn public int total;rnrnrnpublic void setTotal()rn total++;rnrn rn public void run()rn synchronized (this) rnrn System.out.println("ThreadC is running..");rn /*tryrn Thread.sleep(1000);rn catch(InterruptedException e)*/rn for (int i = 0; i < 20; i++) rn total += i;rn System.out.println("total is " + total);rn rn notify();rn rn rnrnrnrnrn这里执行mian方法已经打印的东西,rnb is start....rnThreadC is running..rnWaiting for b to complete...rntotal is 0rntotal is 1rntotal is 3rntotal is 6rntotal is 10rntotal is 15rntotal is 21rntotal is 28rntotal is 36rntotal is 45rntotal is 55rntotal is 66rntotal is 78rntotal is 91rntotal is 105rntotal is 120rntotal is 136rntotal is 153rntotal is 171rntotal is 190rnCompleted.Now back to main threadrnrnrn我这里有一个郁闷的事情,就是从打印ThreadC is running..的时候,那可以看的出来已经进入C线程,而且此时线程拥有此对象的锁(JAVA规定任何时候只有一个线程拥有此对象的锁),但是接着怎么又会打印Waiting for b to complete...呢,这里不是提示A线程也进入这个对象锁了,因为synchronized这个关键字可以获得锁,这样看来不是两个线程同时获得了同一个对象的锁rnrn但是我把public class ThreadC implements Runnable 改成public class ThreadC extends Thread 这个,把Thread b = new Thread(new ThreadC());改成ThreadC b = new ThreadC();,则打印rnb is start....rnThreadC is running..rntotal is 0rntotal is 1rntotal is 3rntotal is 6rntotal is 10rntotal is 15rntotal is 21rntotal is 28rntotal is 36rntotal is 45rntotal is 55rntotal is 66rntotal is 78rntotal is 91rntotal is 105rntotal is 120rntotal is 136rntotal is 153rntotal is 171rntotal is 190rnWaiting for b to complete...rnCompleted.Now back to main threadrnrn在这里打印我认为是正确的,运用了同一个锁,rnrnrn忘高手指教了,还是我理解有错误,谢谢,我都郁闷了好几天了,一直想不通

没有更多推荐了,返回首页