Segment Tree Beats(吉司机线段树)

1

给出 n n n 个数, q q q 次询问,求区间最大子段和,相同的数只算一次。(GSS2)

在线不是很好做,我们离线下来,把询问按 r r r 升序排序,线段树上维护每个 l l l r r r 的和。如果预处理出每个数上一个出现的位置 p r e [ i ] pre[i] pre[i],那么每次右端点右移相当于区间 [ p r e [ i ] + 1 , r ] [pre[i]+1,r] [pre[i]+1,r] a [ i ] a[i] a[i],查询的时候就成了查询区间历史最大值。也就是:
1.区间加
2.区间历史最大值

只需要在线段树上多维护一个历史最大值标记和历史最大值,历史最大值标记表示的是当前节点从上次标记下放到现在,修改标记的最大值。每次 push_down 的时候用根的历史最大值标记和子节点的当前值尝试更新子节点的历史最大值。

void push_down(int root)
{
    int ls=root<<1,rs=root<<1|1;
    if(padd[root])
    {
        pMax[ls]=max(pMax[ls],padd[root]+Max[ls]);
        padd[ls]=max(padd[ls],padd[root]+add[ls]);
        pMax[rs]=max(pMax[rs],padd[root]+Max[rs]);
        padd[rs]=max(padd[rs],padd[root]+add[rs]);
        padd[root]=0;
    }
    if(add[root])
    {
        Max[ls]+=add[root],add[ls]+=add[root];
        Max[rs]+=add[root],add[rs]+=add[root];
        add[root]=0;
    }
}
void push_up(int root)
{
    int ls=root<<1,rs=root<<1|1;
    Max[root]=max(Max[ls],Max[rs]);			//当前最大值
    pMax[root]=max(pMax[ls],pMax[rs]);	//历史最大值
}
void update(int root,int l,int r,int x,int y,int k)
{
    if(x<=l&&y>=r)
    {
        add[root]+=k;
        Max[root]+=k;
        padd[root]=max(padd[root],add[root]);	//历史最大值标记
        pMax[root]=max(pMax[root],Max[root]);
        return;
    }
    push_down(root);
    int mid=l+r>>1;
    if(x<=mid) update(root<<1,l,mid,x,y,k);
    if(y>mid) update(root<<1|1,mid+1,r,x,y,k);
    push_up(root);
}

看起来还是很友好。

2

维护一个序列,支持:
1.区间加。
2.区间赋值。
3.区间所有数对 x x x 取 max。
3.查询区间和。
4.查询区间历史最大值。

开始鬼畜起来了…

首先对于区间取 max,我们在线段树上维护区间最小值、区间次小值以及最小值出现的次数。如果 x x x 小于等于区间最小值,直接返回;如果 x x x 大于区间最小值且小于次小值,就修改区间信息返回;否则递归两个儿子。玄学的势能分析证明复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,并且可以和区间加、区间赋值、区间取 min 等操作组合。

对于其他操作,观察到可以简化为两个操作:区间加,区间取 max。如果用一个二元组表示,那么三个修改分别对应 ( x , − I N F ) , ( − I N F , x ) , ( 0 , x ) (x,-INF),(-INF,x),(0,x) (x,INF),(INF,x),(0,x)。因此,线段树上的标记可以用这样的二元组表示。同样,我们需要维护一个当前标记和“历史最大修改标记”。

当前标记怎么合并?例如 ( a , b ) (a,b) (a,b) ( c , d ) (c,d) (c,d) 合并,我们能得到 ( a + c , m a x ( b + c , d ) ) (a+c,max(b+c,d)) (a+c,max(b+c,d))

历史最大值标记呢?类比上一题,如果父节点的历史最大标记是 p p p,节点的当前标记为 q q q,历史最大标记是 w w w,我们就用 p + q p+q p+q 去尝试更新 w w w,方法是对两维分别取 max。

给出没有询问区间和的代码(洛谷P4314)

#include
#define ll long long
using namespace std;
struct node{
    int x,y;
}ntag[400010],ptag[400010],tmp;
const int INF=1e9;
char s[10];
int Max[400010],pMax[400010],a[100010];
node operator | (node a,node b) {return node{max(a.x,b.x),max(a.y,b.y)};}
node operator * (node a,node b) {return node{max(-INF,a.x+b.x),max(a.y+b.x,b.y)};}
int operator + (int a,node b) {return max(a+b.x,b.y);}

inline int read()
{
    char c=getchar();int x=0,flag=1;
    while(!isdigit(c)){if(c=='-') flag=-1;c=getchar();}
    while(isdigit(c)) x=x*10+c-'0',c=getchar();
    return x*flag;
}
void push_up(int root)
{
    int ls=root<<1,rs=root<<1|1;
    Max[root]=max(Max[ls],Max[rs]);
    pMax[root]=max(pMax[ls],pMax[rs]);
}
void push_down(int root)
{
    int ls=root<<1,rs=root<<1|1;
    ptag[ls]=ptag[ls]|(ntag[ls]*ptag[root]);
    ntag[ls]=ntag[ls]*ntag[root];
    pMax[ls]=max(pMax[ls],Max[ls]+ptag[root]);
    Max[ls]=Max[ls]+ntag[root];
    
    ptag[rs]=ptag[rs]|(ntag[rs]*ptag[root]);
    ntag[rs]=ntag[rs]*ntag[root];
    pMax[rs]=max(pMax[rs],Max[rs]+ptag[root]);
    Max[rs]=Max[rs]+ntag[root];
    
    ntag[root]=ptag[root]=node{0,-INF};
}
void build(int root,int l,int r)
{
    ntag[root]=ptag[root]=node{0,-INF};
    if(l==r) 
    {
        Max[root]=pMax[root]=a[l];
        return;
    }
    int mid=l+r>>1;
    build(root<<1,l,mid);
    build(root<<1|1,mid+1,r);
    push_up(root);	
}
void update(int root,int l,int r,int x,int y)
{
    if(x<=l&&y>=r)
    {
        ntag[root]=ntag[root]*tmp;
        ptag[root]=ptag[root]|ntag[root];
        Max[root]=max(Max[root]+tmp.x,tmp.y);
        pMax[root]=max(pMax[root],Max[root]);
        return;
    }
    int mid=l+r>>1;
    push_down(root);
    if(x<=mid) update(root<<1,l,mid,x,y);
    if(y>mid) update(root<<1|1,mid+1,r,x,y);
    push_up(root);
}
int query(int root,int l,int r,int x,int y,int t)
{
    if(x<=l&&y>=r) return t?pMax[root]:Max[root];
    int mid=l+r>>1,ans=-INF;
    push_down(root);
    if(x<=mid) ans=max(ans,query(root<<1,l,mid,x,y,t));
    if(y>mid) ans=max(ans,query(root<<1|1,mid+1,r,x,y,t));
    push_up(root);
    return ans;
}
int main()
{
    int n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    build(1,1,n);
    int m=read();
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        int x=read(),y=read();
        if(s[0]=='Q') cout<

你可能感兴趣的:(算法解析,线段树)