讲课专用——线段树——最长上升子序列

题目链接:https://www.luogu.org/problemnew/show/P4198

题解:

能用线段树解的关键:从第一个位置开始的LIS

所以说左子区间一定有效,只考虑如何将右子区间与左子区间衔接

线段树维护从节点区间左端点开始的LIS

3种情况:

1、右子区间全都能衔接——右子区间的LIS最小值(第一个位置)> 左子区间LIS最大值

2、右子区间全都不能衔接——右子区间LIS最大值 <= 左子区间LIS最大值

3、右子区间后半部分可以衔接——右子区间某一后半部分LIS最小值 > 左子区间LIS最大值

所以线段树里还要我维护对应的LIS最小值和最大值

#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

#define N 100000
 
int len[N<<2|1];
double mx[N<<2|1],mi[N<<2|1]; 

int n,m;
int ans;

void read(int &x)
{
    x=0; char c=getchar();
    while(!isdigit(c)) c=getchar();
    while(isdigit(c)) { x=x*10+c-'0'; c=getchar(); } 
}

void query(int k,int l,int r,double lim)
{
    if(mi[k]>lim) 
    {
        ans+=len[k];
        return;
    }
    int mid=l+r>>1;
    if(mx[k<<1]>lim) 
    {
        query(k<<1,l,mid,lim);
        query(k<<1|1,mid+1,r,mx[k<<1]);
    }
    else if(mx[k<<1|1]>lim) query(k<<1|1,mid+1,r,lim);
}

void up(int k)
{
    int L=k<<1,R=k<<1|1;
    if(mi[R]>mx[L]) 
    {
        len[k]=len[L]+len[R];
        mx[k]=mx[R];
        if(mi[L]) mi[k]=mi[L];
        else mi[k]=mi[R];
    }
    else if(mx[R]<=mx[L]) 
    {
        len[k]=len[L];
        mx[k]=mx[L];
        mi[k]=mi[L];
    }
    else
    {
        ans=len[L];
        query(R,1,n,mx[L]); 
        len[k]=ans;
        mi[k]=mi[L];
        mx[k]=mx[R];
    }
}

void change(int k,int l,int r,int x,int y)
{
    if(l==r)
    {
        len[k]=1;
        mx[k]=mi[k]=y*1.0/x;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid) change(k<<1,l,mid,x,y);
    else change(k<<1|1,mid+1,r,x,y);
    up(k);
}

void init()
{
    read(n); read(m);
    int x,y; 
    while(m--)
    {
        read(x); read(y);
        change(1,1,n,x,y);
        printf("%d
",len[1]);
    }
}

int main()
{
    init();
    return 0;
} 
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/11142050.html