线段树 Balanced Lineup with Integers

POJ 3264 Balanced Lineup
给定Q (1 ≤ Q ≤ 200,000) 个数A 1 ,A 2 … A Q , ,
多次求任一区间A i – A j 中最大数和最小数的
差。
Sample Input
6 3 //6个数,3次个查询
1
7
3
4
2
5
1 5
4 6
2 2
Sample Output
6
3
0
本题树节点结构:
struct CNode
{
int L,R; // 区间起点和终点
int minV,maxV; // 本区间里的最大最小值
CNode * pLeft, * pRight;
};
也可以不要左右节点指针,用一个数组存放线段
树。根节点下标为0 。假设线段树上某节点下标
为i, 则:
左子节点下标为 i *2+1,
右子节点下标为 i*2+2
如果用一维数组存放线段树,且根节点区间[1,n]
 使用左右节点指针,则数组需要有2n-1 个元素
 不使用左右节点指针,则数组需要有:
2*2^ [log 2 n] -1 个元素 ([log 2 n] 向上取整)
2*2^ [log 2 n] -1 <= 4n -1 , 实际运用时常可以更
小, 可尝试 3n

#include <iostream>
#include <cstdio>
using namespace std;
const int INF=0xffffff0;
int minv=INF;
int maxv=-INF;
struct node
{//不要左右子节点指针的做法
    int L,R;
    int minv,maxv;
    int mid()
    {
        return (R+L)/2;
    }
};
node tree[800010];//4倍叶子节点的数量就够
void buildtree(int root,int L,int R)
{
    tree[root].L=L;
    tree[root].R=R;
    tree[root].minv=INF;
    tree[root].maxv=-INF;
    if(L!=R)
    {
        buildtree(2*root+1,L,(L+R)/2);
        buildtree(2*root+2,(L+R)/2+1,R);
    }
}
void insert(int root,int i,int v)
{//将第i个数,其值为v,插入线段树
    if (tree[root].L==tree[root].R)
    {//成立则亦有 tree[root].R == i
        tree[root].minv=tree[root].maxv=v;
        return ;
    }
    tree[root].minv=min(tree[root].minv,v);
    tree[root].maxv=max(tree[root].maxv,v);
    if(i<=tree[root].mid())
        insert(2*root+1,i,v);
    else
        insert(2*root+2,i,v);
}
void query(int root,int s,int e)
{//查询区间[s,e]中的最小值和最大值,如果更优就记在全局变量里
//minV和maxV里
    if (tree[root].minv>=minv&&tree[root].maxv<=maxv)
        return ;
    if (tree[root].L==s&&tree[root].R==e)
    {
        minv=min(minv,tree[root].minv);
        maxv=max(maxv,tree[root].maxv);
        return ;
    }
    if(e<=tree[root].mid())
        query(2*root+1,s,e);
    else if (s>tree[root].mid())
        query(2*root+2,s,e);
    else
    {
        query(2*root+1,s,tree[root].mid());
        query(2*root+2,tree[root].mid()+1,e);
    }
}
int main()
{
    int n,q,h;
    int i,j,k;
    scanf("%d%d",&n,&q);
    buildtree(0,1,n);
    for (i=1;i<=n;i++)
    {
        scanf("%d",&h);
        insert(0,i,h);
    }
    for (i=0;i<q;i++)
    {
        int s,e;
        scanf("%d%d",&s,&e);
        minv=INF;
        maxv=-INF;
        query(0,s,e);
        printf("%d\n",maxv-minv);
    }
    return 0;
}

POJ 3468 A Simple Problem with Integers
给定Q (1 ≤ Q ≤ 100,000) 个数A 1 ,A 2 … A Q , ,
以及可能多次进行的两个操作:
1) 对某个区间A i … A j 的 每个数 都加n(n 可变)
2) 求某个区间A i … A j 的数的和
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
本题树节点结构:
struct CNode
{
int L ,R;
CNode * pLeft, * pRight;
long long nSum; //原来的和
long long Inc; //增量c的累加
}; // 本节点区间的和实际上是nSum+Inc*(R-L+1)
POJ 3468 A Simple Problem with Integers
在增加时,如果要加的区间正好覆盖一个
节点,则增加其节点的Inc 值,不再往下走
,否则要更新nSum( 加上本次增量), 再将增
量往下传。这样更新的复杂度就是O(log(n))
在查询时,如果待查区间不是正好覆盖一
个节点,就将节点的Inc 往下带,然后将Inc
代表的所有增量累加到nSum 上后将Inc 清0
,接下来再往下查询。 一边查询,一边 Inc 往
下带的过程也是区间分解的过程,复杂度也是
O(log(n))

#include <iostream>
#include <cstdio>
using namespace std;
struct cnode
{
    int L,R;
    cnode *pleft,*pright;
    long long nsum;//原来的和
    long long lnc;//增量c的累加
};
cnode tree[200010];// 2倍叶子节点数目就够
int ncount=0;
int mid(cnode*proot)
{
    return (proot->L+proot->R)/2;
}
void buildtree(cnode *proot,int L,int R)
{
    proot->L=L;
    proot->R=R;
    proot->nsum=0;
    proot->lnc=0;
    if(L==R)
        return ;
    ncount++;
    proot->pleft=tree+ncount;
    ncount++;
    proot->pright=tree+ncount;
    buildtree(proot->pleft,L,(L+R)/2);
    buildtree(proot->pright,(L+R)/2+1,R);
}
void insert(cnode *proot,int i,int v)
{
    if(proot->L==i&&proot->R==i)
    {
        proot->nsum=v;
        return ;
    }
    proot->nsum+=v;
    if(i<=mid(proot))
        insert(proot->pleft,i,v);
    else 
        insert(proot->pright,i,v);
}
void add(cnode *proot,int a,int b,long long c)
{
    if (proot->L==a&&proot->R==b)
    {
        proot->lnc+=c;
        return ;
    }
    proot->nsum+=c*(b-a+1);
    if (b<=(proot->L+proot->R)/2)
        add(proot->pleft,a,b,c);
    else if (a>=(proot->L+proot->R)/2+1)
        add(proot->pright,a,b,c);
    else 
    {
        add(proot->pleft,a,(proot->L+proot->R)/2,c);
        add(proot->pright,(proot->L+proot->R)/2+1,b,c);
    }
}
long long querynsum(cnode *proot,int a,int b)
{
    if (proot->L==a&&proot->R==b)
        return proot->nsum+(proot->R - proot->L+1)*proot->lnc;
    proot->nsum+=(proot->R - proot->L+1)*proot->lnc;
    add(proot->pleft,proot->L,mid(proot),proot->lnc);
    add(proot->pright,mid(proot)+1,proot->R,proot->lnc);
    proot->lnc=0;
    if(b<=mid(proot))
        return querynsum(proot->pleft,a,b);
    else if (a>=mid(proot)+1)
        return querynsum(proot->pright,a,b);
    else
    {
        return querynsum(proot->pleft,a,mid(proot))+
            querynsum(proot->pright,mid(proot)+1,b);
    }
}
int main()
{
    int n,q,a,b,c;
    char cmd[10];
    scanf("%d%d",&n,&q);
    int i,j,k;
    ncount =0;
    buildtree(tree,1,n);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a);
        insert(tree,i,a);
    }
    for (i=0;i<q;i++)
    {
        scanf("%s",cmd);
        if(cmd[0]=='C')
        {
            scanf("%d%d%d",&a,&b,&c);
            add(tree,a,b,c);
        }
        else
        {
            scanf("%d%d",&a,&b);
            printf("%I64d\n",querynsum(tree,a,b));
        }
    }
    return 0;
}

POJ 2528 Mayor’s posters
给定一些海报,可能互相重叠,告诉你每
个海报宽度(高度都一样)和先后叠放次
序,问没有被完全盖住的海报有多少张。
Sample Input
1
5
1 4
2 6
8 10
3 4
7 10
Sample Output
4
思路:依次贴上一张张海报,每贴一张海
报,就询问这张海报有没有被全部遮住
(假设贴海报的过程和现实可以不同,也
可以先贴上面的,再贴下面的)
贴的顺序如何选择?
关键: 插入数据的顺序 —— 从上往下依
次插入每张海报,这样后插入的海报不可
能覆盖先插入的海报,因此插入一张海报
时,如果发现海报对应的瓷砖有一块露出
来,就说明该海报部分可见。
给瓷砖编号,一个海报就相当于一个整数
区间。贴海报就是区间操作,查询海报是
否可见也是区间操作。
因此可以用线椴树来解决
如果每个叶子节点都代表一块瓷砖,那么线段树会导
致MLE,即单位区间的数目太多。而且建树复杂度
O(m),查询复杂度为 nlogm (n是海报数目m是瓷砖数
目)
实际上,由于最多10,000个海报,共计20,000个端点,
这些端点把墙最多分成19,999个单位区间(题意为整
个墙都会被盖到)。每个单位区间的瓷砖数目可以不
同。
我们只要对这19,999个区间编号,然后建树即可。这
就是离散化。
这里写图片描述
这些单位区间在线段树上是叶子节点
. 每个单位区间要么全被覆盖,要么全部露出
. 没有海报的端点会落在一个单位区间内部
. 每张海报一定完整覆盖若干个连续的单位区

. 要算出一共有多少个单位区间,并且算出每
张海报覆盖的单位区间[a,b] (海报覆盖了从a号
单位区间到b号单位区间)
这里写图片描述

#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
int n;
struct CPost
{
int L,R;
};
CPost posters[10100];
int x[20200]; //存放所有海报的端点瓷砖编号
int hash[10000010]; //hash[i]表示瓷砖i所处的离散化后的区间编号
struct CNode
{
int L,R;
bool bCovered; //区间[L,R]是否已经被完全覆盖
CNode * pLeft, * pRight;
};
CNode Tree[1000000];
int nNodeCount = 0;
int Mid( CNode * pRoot)
{
return (pRoot->L + pRoot->R)/2;
}
void BuildTree( CNode * pRoot, int L, int R)
{
pRoot->L = L;
pRoot->R = R;
pRoot->bCovered = false;
if( L == R )
return;
nNodeCount ++;
pRoot->pLeft = Tree + nNodeCount;
nNodeCount ++;
pRoot->pRight = Tree + nNodeCount;
BuildTree( pRoot->pLeft,L,(L+R)/2);
BuildTree( pRoot->pRight,(L+R)/2 + 1,R);
}
bool Post( CNode *pRoot, int L, int R)
{ //插入一张正好覆盖区间[L,R]的海报,返回true则说明区间[L,R]是部分或
全部可见的
if( pRoot->bCovered ) return false;
if( pRoot->L == L && pRoot->R == R) {
pRoot->bCovered = true;
return true;
}
bool bResult ;
if( R <= Mid(pRoot) )
bResult = Post( pRoot->pLeft,L,R);
else if( L >= Mid(pRoot) + 1)
bResult = Post( pRoot->pRight,L,R);
else {
bool b1 = Post(pRoot->pLeft ,L,Mid(pRoot));
bool b2 = Post( pRoot->pRight,Mid(pRoot) + 1,R);
bResult = b1 || b2;
}
//要更新根节点的覆盖情况
if( pRoot->pLeft->bCovered && pRoot->pRight->bCovered )
pRoot->bCovered = true;
return bResult;
}
int main()
{
int t;
int i,j,k;
scanf("%d",&t);
int nCaseNo = 0;
while(t--) {
nCaseNo ++;
scanf("%d",&n);
int nCount = 0;
for( i = 0;i < n;i ++ ) {
scanf("%d%d", & posters[i].L,
& posters[i].R );
x[nCount++] = posters[i].L;
x[nCount++] = posters[i].R;
}
sort(x,x+nCount);
nCount = unique(x,x+nCount) - x; //去掉重复元素
//下面离散化
int nIntervalNo = 0;
for( i = 0;i < nCount;i ++ ) {
hash[x[i]] = nIntervalNo;
if( i < nCount - 1) {
if( x[i+1] - x[i] == 1)
nIntervalNo ++;
else
nIntervalNo += 2;
}
}
BuildTree( Tree,0,nIntervalNo );
int nSum = 0;
for( i = n - 1;i >= 0;i -- ) { // 从后往前看每个海报是否可if( Post(Tree,hash[posters[i].L],
hash[posters[i].R]))
nSum ++;
}
printf("%d\n",nSum);
}
return 0;
}

转自郭炜老师

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值