线段树维护(最大区间和,最大子段和,最长连续上升子序列)

本文主要介绍用线段树来维护(最大区间和,最大子段和,最长连续上升子序列)的问题。


HDU 1540 Tunnel Warfare(最长连续区间+单点修改)

洛谷 P2894 [USACO08FEB]酒店Hotel(最长连续区间+区间修改)

吉首大学2019年程序设计竞赛-白山茶与红玫瑰(最长连续区间+区间修改)

SPOJ - GSS1 Can you answer these queries I(最大子段和)

HDU3308 LCIS(区间最长连续上升子序列)



HDU 1540 Tunnel Warfare(最长连续区间+单点修改)

题意:有三种操作:

操作一:某个村庄被毁灭。

操作二:给出一个村庄的坐标,求包含包含这个村庄的的最长未被村庄的长度。

操作三:最后一个被摧毁的村庄被修复。

题解:首先我们可以想到用线段树来维护最长连续区间长度,我们现在可以用1代表改点没有被破坏,用0表示改点被破坏,然后用一个栈或数组来装上次破坏的点。然后就是线段树维护1的最长长度了,详解请看代码。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls[maxn << 2], rs[maxn << 2], sum[maxn << 2];
///sum表示区间最长连续序列,ls表示从左端点开始最长连续前缀,rs表示从r开始最长后缀
int n, m;

void push_up(int l, int r, int rt) {
    int mid = (l + r) >> 1;
    ls[rt] = ls[rt << 1];
    if (ls[rt << 1] == mid - l + 1)///要是左区间最长长度等于左区间长度,那么区间最长前缀长度要加上右儿子区间的最长前缀
        ls[rt] += ls[rt << 1 | 1];
    rs[rt] = rs[rt << 1 | 1];
    if (rs[rt << 1 | 1] == r - mid)///与上面情况一样
        rs[rt] += rs[rt << 1];
    sum[rt] = max(ls[rt], rs[rt]);
    sum[rt] = max(sum[rt], max(sum[rt << 1], sum[rt << 1 | 1]));
    sum[rt] = max(sum[rt], rs[rt << 1] + ls[rt << 1 | 1]);
    ///区间的最长连续长度为,左区间,右区间最长连续长度,和左区间的最长后缀+右区间的最长前缀的三者最大。(最后一种情况表示最长连续区间在中间)
}

void build(int l, int r, int rt) {
    if (l == r) {
        ls[rt] = rs[rt] = sum[rt] = 1;///初始化所有点都没有被破坏
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    push_up(l, r, rt);
}

void push_date(int pos, int x, int l, int r, int rt) {
    sum[rt] = ls[rt] = rs[rt] = 0;
    if (l == r) {
        sum[rt] = ls[rt] = rs[rt] = x;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        push_date(pos, x, lson);
    else
        push_date(pos, x, rson);
    push_up(l, r, rt);
}

int query(int pos, int l, int r, int rt) {
    if (l == r)
        return sum[rt];
    int mid = (l + r) >> 1;
    if (pos <= mid) {///点在左儿子区间里
        if (pos >= mid - rs[rt << 1] + 1)
            return ls[rt << 1 | 1] + rs[rt << 1];///要是点正好在左儿子的最长后缀中,肯定最长区间和就是左儿子的最长后缀长度+右儿子的最长前缀(左儿子的最长后缀与右儿子的最长前缀是连着的)
        else
            return query(pos, lson);///要是没有落在最长后缀中,就不用考虑是否加上右儿子的最长前缀,直接往下找就行了
    } else {
        if (pos <= mid + ls[rt << 1 | 1])///与上面情况类似
            return ls[rt << 1 | 1] + rs[rt << 1];
        else
            return query(pos, rson);
    }
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        build(1, n, 1);
        stack q;
        while (m--) {
            char str[2];
            int x;
            scanf("%s", str);
            if (str[0] == 'D') {
                scanf("%d", &x);
                push_date(x, 0, 1, n, 1);
                q.push(x);
            } else if (str[0] == 'Q') {
                scanf("%d", &x);
                printf("%d\n", query(x, 1, n, 1));
            } else {
                if (q.size()) {
                    push_date(q.top(), 1, 1, n, 1);
                    q.pop();
                }
            }
        }
    }
    return 0;
}

 

洛谷 P2894 [USACO08FEB]酒店Hotel(最长连续区间+区间修改)

题意:有n代表有n个房间,编号为1-n,开始都为空房,有m行操作,以下 每行先输入一个数 i ,表示一种操作:

若i为1,表示查询房间,再输入一个数x,表示在1-n 房间中找到长度为x的连续空房,输出连续x个房间中左端的房间号,尽量让这个房间号最小,若找不到长度为x的连续空房,输出0。

若i为2,表示退房,再输入两个数 x,y 代表 让房间号 x-x+y-1 的房间为空。

题解:这是一个最长连续区间,区间修改的裸题,只是注意找连续区间最左端的房间号(可以好好理解下下面代码的Find_pos函数)。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 5e4 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls[maxn << 2], rs[maxn << 2], sum[maxn << 2];
int lazy[maxn << 2];
int n, m;

void push_up(int l, int r, int rt) {
    int mid = (l + r) >> 1;
    ls[rt] = ls[rt << 1];
    if (ls[rt << 1] == mid - l + 1)
        ls[rt] += ls[rt << 1 | 1];
    rs[rt] = rs[rt << 1 | 1];
    if (rs[rt << 1 | 1] == r - mid)
        rs[rt] += rs[rt << 1];
    sum[rt] = max(ls[rt], rs[rt]);
    sum[rt] = max(sum[rt], max(sum[rt << 1], sum[rt << 1 | 1]));
    sum[rt] = max(sum[rt], rs[rt << 1] + ls[rt << 1 | 1]);
}

void push_down(int l, int r, int rt) {
    if (lazy[rt] != -1) {
        int mid = (l + r) >> 1;
        lazy[rt << 1] = lazy[rt << 1 | 1] = lazy[rt];
        sum[rt << 1] = ls[rt << 1] = rs[rt << 1] = (mid - l + 1) * lazy[rt];
        sum[rt << 1 | 1] = ls[rt << 1 | 1] = rs[rt << 1 | 1] = (r - mid) * lazy[rt];
        lazy[rt] = -1;
    }
}

void build(int l, int r, int rt) {
    sum[rt] = ls[rt] = rs[rt] = 0;
    if (l == r) {
        sum[rt] = ls[rt] = rs[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);
    build(rson);
    push_up(l, r, rt);
}

void push_date(int L, int R, int opt, int l, int r, int rt) {
    if (L <= l && R >= r) {
        sum[rt] = ls[rt] = rs[rt] = (r - l + 1) * opt;
        lazy[rt] = opt;
        return;
    }
    push_down(l, r, rt);
    int mid = (l + r) >> 1;
    if (R <= mid)
        push_date(L, R, opt, lson);
    else if (L > mid)
        push_date(L, R, opt, rson);
    else {
        push_date(L, mid, opt, lson);
        push_date(mid + 1, R, opt, rson);
    }
    push_up(l, r, rt);
}

int Find_pos(int len, int l, int r, int rt) {
    if(l==r)
        return l;
    push_down(l, r, rt);
    int mid = (l + r) >> 1;
    if(len<=sum[rt<<1])
        return Find_pos(len,lson);
    else if(rs[rt<<1]+ls[rt<<1|1]>=len)
        return mid-rs[rt<<1]+1;
    else
        return Find_pos(len,rson);
}

int main() {
    me(lazy, -1);
    scanf("%d%d", &n, &m);
    build(1, n, 1);
    while (m--) {
        int opt, l, len;
        scanf("%d", &opt);
        if (opt == 1) {
            scanf("%d", &len);
            if (len <= sum[1]) {
                int pos = Find_pos(len, 1, n, 1);
                printf("%d\n", pos);
                push_date(pos, pos+len-1, 0, 1, n, 1);
            } else
                printf("0\n");
        } else {
            scanf("%d%d", &l, &len);
            push_date(l, l + len - 1, 1, 1, n, 1);
        }
    }
    return 0;
}

 

吉首大学2019年程序设计竞赛-白山茶与红玫瑰(最长连续区间+区间修改)

题意:现在给出一段序列,只有0和1,现在有两种操作:

操作一:给出[l,r],求出该区间内最长连续0区间的长度。

操作二:给出[l,r],将该区间的0全部变成1,1全部变成0。

题解:现在建立两棵线段树,一个保存最长连续1区间长度,一个保存最长连续0区间长度,在执行操作一:就是常规求法就行了,在进行操作二时,将对应区间的对应数组的值全部交换,这样两者刚好反过来,就是我们要求的值。

注意:因为翻转两次就等于没有翻转,所以lazy数组,初值为0,1表示要翻转,然后每次修改都将对应lazy数组值异或1就行了。

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int maxn = 1e5 + 5;
const int mod = 10007;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson L,mid,rt<<1
#define rson mid+1,R,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int ls1[maxn << 2], rs1[maxn << 2], sum1[maxn << 2];
int ls2[maxn << 2], rs2[maxn << 2], sum2[maxn << 2];
int lazy[maxn << 2];
int n, m, x;

void push_up(int l, int r, int rt) {
    int mid = (l + r) >> 1;
    ls1[rt] = ls1[rt << 1];
    if (ls1[rt << 1] == mid - l + 1)
        ls1[rt] += ls1[rt << 1 | 1];
    rs1[rt] = rs1[rt << 1 | 1];
    if (rs1[rt << 1 | 1] == r - mid)
        rs1[rt] += rs1[rt << 1];
    sum1[rt] = max(sum1[rt << 1], sum1[rt << 1 | 1]);
    sum1[rt] = max(sum1[rt], max(ls1[rt], rs1[rt]));
    sum1[rt] = max(sum1[rt], rs1[rt << 1] + ls1[rt << 1 | 1]);
    ls2[rt] = ls2[rt << 1];
    if (ls2[rt << 1] == mid - l + 1)
        ls2[rt] += ls2[rt << 1 | 1];
    rs2[rt] = rs2[rt << 1 | 1];
    if (rs2[rt << 1 | 1] == r - mid)
        rs2[rt] += rs2[rt << 1];
    sum2[rt] = max(sum2[rt << 1], sum2[rt << 1 | 1]);
    sum2[rt] = max(sum2[rt], max(ls2[rt], rs2[rt]));
    sum2[rt] = max(sum2[rt], rs2[rt << 1] + ls2[rt << 1 | 1]);
}

void push_down(int rt) {
    if (lazy[rt]) {
        lazy[rt << 1] ^= 1;
        lazy[rt << 1 | 1] ^= 1;
        swap(ls1[rt << 1], ls2[rt << 1]);
        swap(rs1[rt << 1], rs2[rt << 1]);
        swap(sum1[rt << 1], sum2[rt << 1]);
        swap(ls1[rt << 1 | 1], ls2[rt << 1 | 1]);
        swap(rs1[rt << 1 | 1], rs2[rt << 1 | 1]);
        swap(sum1[rt << 1 | 1], sum2[rt << 1 | 1]);
        lazy[rt] = 0;
    }
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &x);
        ls1[rt] = rs1[rt] = sum1[rt] = x;
        if (x == 1)
            ls2[rt] = rs2[rt] = sum2[rt] = 0;
        else
            ls2[rt] = rs2[rt] = sum2[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(l, mid, rt << 1);
    build(mid + 1, r, rt << 1 | 1);
    push_up(l, r, rt);
}

void push_date(int l, int r, int L, int R, int rt) {
    if (L <= l && R >= r) {
        lazy[rt] ^= 1;
        swap(ls1[rt], ls2[rt]);
        swap(rs1[rt], rs2[rt]);
        swap(sum1[rt], sum2[rt]);
        return;
    }
    push_down(rt);
    int mid = (l + r) >> 1;
    if (R <= mid)
        push_date(l, mid, L, R, rt << 1);
    else if (L > mid)
        push_date(mid + 1, r, L, R, rt << 1 | 1);
    else {
        push_date(l, mid, L, mid, rt << 1);
        push_date(mid + 1, r, mid + 1, R, rt << 1 | 1);
    }
    push_up(l, r, rt);
}

int query(int l, int r, int L, int R, int rt) {
    if (L <= l && R >= r)
        return sum1[rt];
    push_down(rt);
    int mid = (l + r) >> 1;
    if (R <= mid) {
        return query(l, mid, L, R, rt << 1);
    } else if (L > mid) {
        return query(mid + 1, r, L, R, rt << 1 | 1);
    } else {
        int len1 = query(l, mid, L, mid, rt << 1);
        int len2 = query(mid + 1, r, mid + 1, R, rt << 1 | 1);
        int len3 = min(mid - L + 1, rs1[rt << 1]) + min(R - mid, ls1[rt << 1 | 1]);
        return max(len1, max(len2, len3));
    }
    return 0;
}

int main() {
    scanf("%d", &n);
    build(1, n, 1);
    scanf("%d", &m);
    while (m--) {
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if (opt == 1)
            push_date(1, n, l, r, 1);
        else
            printf("%d\n", query(1, n, l, r, 1));
    }
    return 0;
}

SPOJ - GSS1 Can you answer these queries I(最大子段和)

题意:给出一段序列,给出m次询问,让你给出该区间内最大连续子段和。

题解:跟最长连续子序列差不多,这是一道模板题,直接上代码。

#pragma comment(linker, "/STACK:102400000,102400000")

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int mod = 998244353;
const int maxn = 5e4 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define Lson l,mid,rt<<1
#define Rson mid+1,r,rt<<1|1
#define lson rt<<1
#define rson rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
struct node {
    int val, lmax, rmax, max;
} tree[maxn << 2];

void push_up(int rt) {
    tree[rt].max = max(max(tree[lson].max, tree[rson].max), tree[lson].rmax + tree[rson].lmax);
    tree[rt].lmax = max(tree[lson].lmax, tree[lson].val + tree[rson].lmax);
    tree[rt].rmax = max(tree[rson].rmax, tree[rson].val + tree[lson].rmax);
    tree[rt].val = tree[lson].val + tree[rson].val;
}

void build(int l, int r, int rt) {
    if (l == r) {
        scanf("%d", &tree[rt].val);
        tree[rt].lmax = tree[rt].rmax = tree[rt].max = tree[rt].val;
        return;
    }
    int mid = (l + r) >> 1;
    build(Lson);
    build(Rson);
    push_up(rt);
}

node query(int ql, int qr, int l, int r, int rt) {
    if (ql <= l && qr >= r)
        return tree[rt];
    int mid = (l + r) >> 1;
    if (qr <= mid)
        return query(ql, qr, Lson);
    else if (ql > mid)
        return query(ql, qr, Rson);
    else {
        node L = query(ql, mid, Lson);
        node R = query(mid + 1, qr, Rson);
        node ans;
        ans.max = max(max(L.max, R.max), L.rmax + R.lmax);
        ans.lmax = max(L.lmax, L.val + R.lmax);
        ans.rmax = max(R.rmax, R.val + L.rmax);
        return ans;
    }
}

int main() {
    int n, m;
    scanf("%d", &n);
    build(1, n, 1);
    scanf("%d", &m);
    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", query(l, r, 1, n, 1).max);
    }
    return 0;
}

HDU3308 LCIS(区间最长连续上升子序列)

题意:求区间最长连续上升子序列。

PS:因为要更改节点的值,所以不能用dp做。这里就可以用线段树来维护。

这就是一个裸的线段树求最长上升子序列的题,细节看代码,主要是要注意更新左子树,右子树,和区间最长子序列长度。

#pragma comment(linker, "/STACK:102400000,102400000")

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include

const int mod = 998244353;
const int maxn = 1e5 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define Lson l,mid,rt<<1
#define Rson mid+1,r,rt<<1|1
#define lson rt<<1
#define rson rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

int a[maxn], sum[maxn << 2], ls[maxn << 2], rs[maxn << 2];

void updata(int l, int r, int rt)///更新区间最长子序列长度
{
    int mid = (l + r) >> 1;
    ls[rt] = ls[lson], rs[rt] = rs[rson];
    sum[rt] = max(sum[lson], sum[rson]);
    if (a[mid] < a[mid + 1]) {///当满足这个条件,说明可以左儿子右儿子合并
        if (ls[rt] == mid - l + 1)///当左儿子区间全部都是上升子序列,又能区间合并,这时候左儿子区间加上右儿子的左区间
            ls[rt] = ls[lson] + ls[rson];
        if (rs[rt] == r - mid)///与上面类似
            rs[rt] = rs[rson] + rs[lson];
        sum[rt] = max(sum[rt], ls[rson] + rs[lson]);
    }
}

void build(int l, int r, int rt)///建树
{
    if (l == r) {
        sum[rt] = ls[rt] = rs[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(Lson);
    build(Rson);
    updata(l, r, rt);
}

void pushdata(int pos, int c, int l, int r, int rt)///更改节点值
{
    if (l == r) {
        a[l] = c;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        pushdata(pos, c, Lson);
    else
        pushdata(pos, c, Rson);
    updata(l, r, rt);
}

int query(int L, int R, int l, int r, int rt)///求出最长子序列长度
{
    if (l >= L && R >= r)
        return sum[rt];
    int ret = 0;
    int mid = (l + r) >> 1;
    if (mid >= L)
        ret = max(ret, query(L, R, Lson));
    if (mid < R)
        ret = max(ret, query(L, R, Rson));
    if (a[mid] < a[mid + 1]) {
        ret = max(ret, min(mid - L + 1, rs[lson]) + min(R - mid, ls[rson]));
        ///[m+1,R]与[m+1,r]相交部分的最大前缀+[L,m]与[l,m]的最大后缀.
    }
    return ret;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        build(1, n, 1);
        while (m--) {
            char s[2];
            int a, b;
            scanf("%s%d%d", s, &a, &b);
            if (s[0] == 'U')
                pushdata(a + 1, b, 1, n, 1);
            else
                printf("%d\n", query(a + 1, b + 1, 1, n, 1));
        }
    }
    return 0;
}

 

你可能感兴趣的:(线段树,数据结构)