(线段树笔记)

88 阅读5分钟

线段树题集 线段树问题的一般思路: 1:单点修改还是区间修改 ----> 是否需要用到懒标记 2:维护什么信息? 能不能由左右儿子计算得到?

1:最大值

可以对这列数进行两种操作:

添加操作:向序列后添加一个数,序列长度变成 n+1; 询问操作:询问这个序列中最后 L 个数中最大的数是多少。 程序运行的最开始,整数序列为空。 一共要对整数序列进行 m 次操作。

写一个程序,读入操作的序列,并输出询问操作的答案。

1:单点修改 2:维护最大值 ---->可以由左右子树计算得到

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
#define all(x) x.begin(), x.end()
#define maxi(x) max_element(x.begin(), x.end()) - x.begin()
#define mini(x) min_element(x.begin(), x.end()) - x.begin()
const int maxn = 2e5 + 10;

struct Node
{
    int l, r;
    int v;
} tr[maxn * 4];

void push_up(int u)
{
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if (l == r)
        return;
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    push_up(u);
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].v;
    int mid = tr[u].l + tr[u].r >> 1;
    int v = 0;
    if (l <= mid)
        v = query(u << 1, l, r);
    if (r > mid)
        v = max(v, query(u << 1 | 1, l, r));
    return v;
}

void modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x)
        tr[u].v = v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid)
            modify(u << 1, x, v);
        else
            modify(u << 1 | 1, x, v);
        push_up(u);
    }
} 

void solve()
{
    int n = 0, last = 0;
    int m, p;
    cin >> m >> p;
    build(1, 1, m);
    char op;
    int x;
    while (m--)
    {
        cin >> op >> x;
        if (op == 'Q')
        {
            last = query(1, n - x + 1, n);
            cout << last << endl;
        }
        else
        {
            modify(1, n + 1, (last + x) % p);
            n++;
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    //cin>>t;
    while (t--)
    {
        solve();
    }
    return 0;
}

2:最长连续子段和

给定长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:

1 x y,查询区间 [x,y] 中的最大连续子段和。 2 x y,把 A[x] 改成 y。 对于每个查询指令,输出一个整数表示答案。

1:单点修改。 2:维护最长连续子段和---->不能直接由左右子树计算得到。 考虑三种情况。 maxv = max ({L.tmax , R.tmax , L.rmax + R.lmax }); 即,最长连续子段和可能是左右子树最长连续子段和,也可能是横跨两个子树的子段和。 即左子树的最大后缀子段和 与 右子树的最大前缀子段和 的和值。 在这里插入图片描述 进一步考虑,上述操作所依赖的最大前(后)缀子段和,也不能由其左右子树直接得到。 在这里插入图片描述 可以看到,最大后缀子段和可能是其右子树的最大连续子段和,也有可能是右子树的和与左子树最大连续子段和的和值。即 rmax =max(R.rmax , R.sum + L.rmax ) ; 而每个结点的和显然可以由左右子树计算得到。至此,每个结点将可以由其左右子树计算得到。

struct node
{
    int l, r;
    //和、最大前缀子段和、最大后缀子段和、最大连续子段和。
    int sum, lmax, rmax, tmax;
} tr[maxn * 4];
void push_up(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max({l.tmax, r.tmax, l.rmax + r.lmax});
}

void push_up(int u)
{
    push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
#define all(x) x.begin(), x.end()
#define maxi(x) max_element(x.begin(), x.end()) - x.begin()
#define mini(x) min_element(x.begin(), x.end()) - x.begin()
const int maxn = 5e5 + 10;
int n, m;
int w[maxn];
struct node
{
    int l, r;
    int sum, lmax, rmax, tmax;
} tr[maxn * 4];

void push_up(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max({l.tmax, r.tmax, l.rmax + r.lmax});
}

void push_up(int u)
{
    push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l == r)
        tr[u] = {l, r, w[r], w[r], w[r], w[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

int modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x)
        tr[u] = {x, x, v, v, v, v};
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid)
            modify(u << 1, x, v);
        else
            modify(u << 1 | 1, x, v);
        push_up(u);
    }
}

node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid)
            return query(u << 1, l, r);
        else if (l > mid)
            return query(u << 1 | 1, l, r);
        else
        {
            auto lch = query(u << 1, l, r);
            auto rch = query(u << 1 | 1, l, r);
            node res;
            push_up(res, lch, rch);
            return res;
        }
    }
}

void solve()
{
    cin>>n>>m;
    for (int i = 1; i <= n; i++)
        cin >> w[i];
    build(1, 1, n);
    int k, x, y;
    while (m--)
    {
        cin >> k >> x >> y;
        if (k == 1)
        {
            if (x > y)
                swap(x, y);
            cout << query(1, x, y).tmax << endl;
        }
        else
            modify(1, x, y);
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    //cin>>t;
    while (t--)
    {
        solve();
    }
    return 0;
}

3:最大公约数

给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一: C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。 Q l r,表示询问 A[l],A[l+1],…,A[r] 的最大公约数(GCD)。 对于每个询问,输出一个整数表示答案。

1、区间修改? 有一种巧妙的办法可以讲区间修改变成单点修改 由gcd(a,b)=gcd(a,b-a) 易得gcd(a,b,c)=gcd(a,b-a,c-b) 则可以用线段树\树状数组来维护差分数组,从而将区间修改转化为单点修改

2、维护gcd(al,al+1,...ar) ---> 可以由左右子树计算得到

struct node
{
    int l, r;
    ll sum, d;
} tr[maxn * 4];
void push_up(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void push_up(int u)
{
    push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
#define all(x) x.begin(), x.end()
#define maxi(x) max_element(x.begin(), x.end()) - x.begin()
#define mini(x) min_element(x.begin(), x.end()) - x.begin()
int n, m;
const int maxn = 5e5 + 10;
ll w[maxn];

struct node
{
    int l, r;
    ll sum, d;
} tr[maxn * 4];

ll gcd(ll a, ll b)
{
    return b ? gcd(b, a % b) : a;
}

void push_up(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void push_up(int u)
{
    push_up(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    ll tmp = w[r] - w[r - 1];
    if (l == r)
        tr[u] = {l, r, tmp, tmp};
    else
    {
        tr[u].l = l, tr[u].r = r;
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

void modify(int u, int x, ll v)
{
    if (tr[u].l == x && tr[u].r == x)
    {
        ll b = tr[u].sum + v;
        tr[u] = {x, x, b, b};
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid)
            modify(u << 1, x, v);
        else
            modify(u << 1 | 1, x, v);
        push_up(u);
    }
}

node query(int u, int l, int r)
{
    if (l > r)
        return {};
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid)
            return query(u << 1, l, r);
        else if (l > mid)
            return query(u << 1 | 1, l, r);
        else
        {
            auto lch = query(u << 1, l, r);
            auto rch = query(u << 1 | 1, l, r);
            node res;
            push_up(res, lch, rch);
            return res;
        }
    }
}

void solve()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> w[i];
    build(1, 1, n);
    int l, r;
    ll d;
    char op;
    while (m--)
    {
        cin >> op >> l >> r;
        if (op == 'Q')
        {
            auto lch = query(1, 1, l);
            auto rch = query(1, l + 1, r);
            cout << gcd(lch.sum, rch.d);
        }
        else
        {
            cin >> d;
            modify(1, l, d);
            if (r + 1 <= n)
                modify(1, r + 1, -d);
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    //cin>>t;
    while (t--)
    {
        solve();
    }
    return 0;
}

4:CF1549d

British mathematician John Littlewood once said about Indian mathematician Srinivasa Ramanujan that "every positive integer was one of his personal friends."

It turns out that positive integers can also be friends with each other! You are given an array a of distinct positive integers.

Define a subarray ai,ai+1,…,aj to be a friend group if and only if there exists an integer m≥2 such that aimodm=ai+1modm=…=ajmodm, where xmody denotes the remainder when x is divided by y.

Your friend Gregor wants to know the size of the largest friend group in a.

Input Each test contains multiple test cases. The first line contains the number of test cases t (1≤t≤2⋅104).

Each test case begins with a line containing the integer n (1≤n≤2⋅105), the size of the array a.

The next line contains n positive integers a1,a2,…,an (1≤ai≤1018), representing the contents of the array a. It is guaranteed that all the numbers in a are distinct.

It is guaranteed that the sum of n over all test cases is less than 2⋅105.

Output Your output should consist of t lines. Each line should consist of a single integer, the size of the largest friend group in a.

线段树维护gcd。 在第三棵树的基础上用双指针维护答案。 本题还有st表做法,不谈。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
#define all(x) x.begin(), x.end()
#define maxi(x) max_element(x.begin(), x.end()) - x.begin()
#define mini(x) min_element(x.begin(), x.end()) - x.begin()
#define int long long
const int maxn = 2e5 + 10;
int a[maxn];

struct node
{
    int l, r;
    int val;
} tr[maxn * 4];

void push_up(int u)
{
    tr[u].val = __gcd(tr[u << 1].val, tr[u << 1 | 1].val);
}

void build(int u, int l, int r)
{
    if (l == r)
        tr[u] = {l, r, a[l]};
    else
    {
        tr[u].l = l, tr[u].r = r;
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        push_up(u);
    }
}

int query(int u, int l, int r)
{
    if (l <= tr[u].l && r >= tr[u].r)
        return tr[u].val;
    if (l > r)
        return tr[u].val;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (mid >= r)
            return query(u << 1, l, r);
        else if (mid < l)
            return query(u << 1 | 1, l, r);
        else
            return __gcd(query(u << 1, l, mid), query(u << 1 | 1, mid + 1, r));
    }
}

void solve()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n - 1; i++)
        a[i] = abs(a[i] - a[i + 1]);
    build(1, 1, n);
    int ans = 0;
    int l = 1, r = 1;
    while (r <= n - 1)
    {
        while (query(1, l, r) != 1 && r <= n - 1)
            r++;
        ans = max(ans, r - l);
        while (query(1, l, r) == 1 && l <= r - 1)
            l++;
        if (a[l] == 1)
        {
            l++;
            r++;
        }
    }
    cout << ans + 1 << endl;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}