cf739 C. Alyona and towers(线段树)


题意:

给定数组,要求实现两种操作:区间加、查询数组中最长的 “先严格上升再严格下降” 的连续子区间的长度。

注意只严格上升或只严格下降或只有一个元素也是合法的。

思路:

把长为n的原数组处理成相邻项的差,长为n-1。w[i]表示第i+1个数与第i个数的差。

考虑相邻项的差的符号,1,0,-1表示大于零,等于零,小于零。满足条件的形式为 \(+1,+1,+1,-1,-1\) ,即差的符号函数值非严格递减,不能出现0。

用线段树维护差的符号函数值,记录区间最大的连续非严格递减子列的长度、从左端点开始的长度和从右端点开始的长度。合并区间时要判断左右儿子相接的地方能不能合并。

因为差分了所以只需单点修改。查询时直接输出根节点上的答案。另外再实现个建树函数和pushup即可,不需要pushdown。pushup和main函数中有挺多细节,其他几个函数跟普通线段树差不多。

对于每次修改,首先直接修改差分数组(即原数组),如果改变了差的符号才需要改线段树。

n=1时根本没有相邻项,特判一下。

#include 
using namespace std;
using ll = long long;
const int N = 3e5 + 5;
ll w[N];
int sgn(ll x) { //返回x的符号
    if(x > 0) return 1; if(x < 0) return -1;
    return 0;
}

struct node {
    int l, r, len, llen, rlen;
} tr[N*4];
void pushup(int u)
{
    tr[u].len = max(tr[u<<1].len, tr[u<<1|1].len);
    tr[u].llen = tr[u<<1].llen, tr[u].rlen = tr[u<<1|1].rlen;
    if(w[tr[u<<1].r] && w[tr[u<<1|1].l] && //不能是0
        sgn(w[tr[u<<1].r]) >= sgn(w[tr[u<<1|1].l])) //且须递减
    {
        tr[u].len = max(tr[u].len, tr[u<<1].rlen + tr[u<<1|1].llen);
        if(tr[u<<1].llen == tr[u<<1].r - tr[u<<1].l + 1)
            tr[u].llen += tr[u<<1|1].llen;
        if(tr[u<<1|1].rlen == tr[u<<1|1].r - tr[u<<1|1].l + 1)
            tr[u].rlen += tr[u<<1].rlen;
    }
}
void build(int u, int l, int r)
{
    if(l == r) //只维护符号
    {
        bool tmp = w[l];
        tr[u] = {l, r, tmp, tmp, tmp};
    }
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u<<1, l, mid), build(u<<1|1, mid+1, r);
        pushup(u);
    }
}
void modify(int u, int p, int x) //把单点p改为x
{
    if(tr[u].l == tr[u].r)
    {
        bool tmp = x;
        tr[u] = {tr[u].l, tr[u].r, tmp, tmp, tmp};
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if(p <= mid) modify(u<<1, p, x);
    else modify(u<<1|1, p, x);
    pushup(u);
}

signed main()
{
    int n, q; scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%lld", &w[i]);

    if(n == 1) {
        scanf("%d", &q); while(q--) puts("1");
        return 0;
    }

    for(int i = 1; i < n; i++) w[i] = w[i+1] - w[i]; //差分
    build(1, 1, n-1); //维护n-1个相邻差

    scanf("%d", &q); while(q--)
    {
        int l, r, d; scanf("%d%d%d", &l, &r, &d);
        //pushup里面要用新的w[i],所以必须先更新w再修改
        if(l > 1) {
            if(sgn(w[l-1]) != sgn(w[l-1]+d))
                 w[l-1] += d, modify(1, l-1, sgn(w[l-1]));
            else w[l-1] += d;
        }
        if(r < n) {
            if(sgn(w[r]) != sgn(w[r]-d))
                w[r] -= d, modify(1, r, sgn(w[r]));
            else w[r] -= d;
        }

        printf("%d\n", tr[1].len + 1);
    }

    return 0;
}