HDU 3308 LCIS (线段树&&最长递增子序列长度)

来源:互联网 发布:阿里云主机怎么使用 编辑:程序博客网 时间:2024/06/05 03:45

传送门 :http://acm.hdu.edu.cn/showproblem.php?pid=3308

Problem Description
Given n integers.
You have two operations:
U A B: replace the Ath number by B. (index counting from 0)
Q A B: output the length of the longest consecutive increasing subsequence (LCIS) in [a, b].

Input
T in the first line, indicating the case number.
Each case starts with two integers n , m( 0< n,m<=105).
The next line has n integers(0<=val<=105).
The next m lines each has an operation:
U A B(0<=A,n , 0<=B=105)
OR
Q A B(0<=A<=B< n).

Output
For each Q, output the answer.

Sample Input
1
10 10
7 7 3 3 5 9 9 8 1 8
Q 6 6
U 3 4
Q 0 1
Q 0 5
Q 4 7
Q 3 5
Q 0 2
Q 4 6
U 6 10
Q 0 9

Sample Output
1
1
4
2
3
1
2
5

题目大意:
给你n个数,m个操作。操作有两种:1.U x y 将数组第x位变为y 2. Q x y 问数组第x位到第y位连续最长子序列的长度。对于每次询问,输出一个答案

目前做的最难一道线段树题了, 比较弱,比较弱

这是一道比较典型的 单点更新,区间合并的题目

建立线段树还是常规建法 ,不过这里建树时,直接在外部输入每个点的值
建树主要在于更新 ls, rs ,no的值 (ls代表区间递增前缀长度, rs代表递增区间后缀长度, no代表最大的递增子序列长度 ) ###(重点)

难处理的在于 更新节点时 要处理 最长递增子序列的长度 ,可以分为以下2种情况

1.如果左区间的最右边的值小于右区间最左边的值,则有一个待定答案是左儿子的右区间+右儿子的左区间

2.如果不符合第一个条件,则有一个待定答案是左区间最大值和右区间最大值中较大的那一个。

有一点要特别注意:如果当前区间中所有的值都符合上升序列,那么更新它的父节点时,它可以加上其他区间的边界值而进行扩充

举个栗子 画个演示图
求3 1 2 4 5 的最大递增子序列长度(显示补图)
code:

#include <iostream>//#include <bits/stdc++.h>#include <string.h>#include <cstdio>#include <algorithm>using namespace std;#define lchild left, mid, root<<1#define rchild mid+1, right, root<<1|1#define maxn 200000int a[maxn<<2];     ///a数组存储值int ls[maxn<<2],rs[maxn<<2],no[maxn<<2]; ///ls代表区间前缀长度, rs代表区间后缀长度, no代表最大的子序列长度int x,y;void update(int left, int right, int root){    int mid=left+right>>1;    ls[root]=ls[root<<1];rs[root]=rs[root<<1|1];   ///向上更新当前的ls, rs 和 no    no[root]=max(no[root<<1],no[root<<1|1]);    if(a[mid]<a[mid+1])             ///如果a[mid]<a[mid+1]即2个区间可以合并    {        if(ls[root]==mid-left+1)   ///判断从left到mid是完整的递增            ls[root]+=ls[root<<1|1];        if(rs[root]==right-mid)   ///判断从mid+1到right是完整的递增            rs[root]+=rs[root<<1];        no[root]=max(no[root],ls[root<<1|1]+rs[root<<1]);    ///左区间的后缀加上右区间的前缀即合并后的no值    }}void build(int left, int right, int root){    if (left==right)    {        no[root]=ls[root]=rs[root]=1;   ///找到叶子节点 初始化        return;    }    int mid = (left+right)>>1;    build(lchild);    build(rchild);    update(left, right, root);}void operate(int left, int right, int root){    if (left==right)    {        return;    }    int mid = (left+right)>>1;    if (x<=mid)        operate(lchild);    else        operate(rchild);    update(left, right, root);}int inquery(int l, int r, int left, int right, int root){    //cout<<l<<" "<<r<<" "<<left<<" "<<right<<" "<<sum[root]<<endl;    if (l<=left&&r>=right)        return no[root];    int ans;    int mid = (left+right)>>1;    if (r<=mid)   ///右边界小于重点,只用返回左边计算的值即可        return inquery(l,r,lchild);    if (l>mid)         return inquery(l,r,rchild);    int t1=inquery(l,r,lchild);   ///左右边界分开查询    int t2=inquery(l,r,rchild);    ans=max(t1,t2);    if(a[mid]<a[mid+1])        ans=max(ans,(min(ls[root<<1|1],r-mid)+min(rs[root<<1],mid+1-l)));    return ans;}int main(){   int N;   int m,n;   scanf("%d",&N);   while (N--)   {       scanf("%d%d",&n,&m);       for(int i=1; i<=n; i++)        scanf("%d",&a[i]);       build(1,n,1);       while (m--)       {           char str[3];            getchar();            scanf("%s",str);            //cout<<"!!!"<<str<<endl;            scanf("%d%d",&x,&y);            x++;            if (str[0]=='Q')            {                y++;                int ans;                ans = inquery(x,y,1,n,1);                printf("%d\n",ans);            }            else if (str[0]=='U')            {                a[x] = y;                operate(1,n,1);            }       }   }    return 0;}
阅读全文
1 0
原创粉丝点击