暑假集训专题练习记录———线段树

题目列表

A 敌兵布阵(HDU1166)

单点修改,区间查询。因为操作比较简单,所以直接用树状数组就可以了。

AC 代码

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
using namespace std;

#define ll long long
#define endl "\n"

const int max_n = 5e4 + 100;
int tr[max_n];
int n;

void init()
{
    for (int i = 1; i <= n; i++)
    {
        tr[i] = 0;
    }
}

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int c)
{
    for (int i = x; i <= n; i += lowbit(i))
        tr[i] += c;
}

int sum(int x)
{
    int ans = 0;
    for (int i = x; i; i -= lowbit(i))
        ans += tr[i];
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    for (int k = 1; k <= T; k++)
    {
        cin >> n;
        init();
        for (int i = 1; i <= n; i++)
        {
            int tmp;
            cin >> tmp;
            add(i, tmp);
        }
        string s;
        cout << "Case " << k << ":" << endl;
        while (cin >> s)
        {
            if (s == "End")
                break;
            int x, c;
            cin >> x >> c;
            if (s == "Query")
            {
                cout << sum(c) - sum(x - 1) << endl;
            }
            else if (s == "Add")
            {
                add(x, c);
            }
            else if (s == "Sub")
            {
                add(x, -c);
            }
        }
    }

    return 0;
}

B I Hate It(HDU1754)

单点修改,区间查询最大值。可以直接用线段树来写,只用维护一个最大值就行了。因为只涉及到了单点修改,所以并不需要懒标记。

AC代码

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 2e5 + 100;

struct Node
{
    int l, r;
    int v; //最大值
} tr[max_n * 4];
int arr[max_n];
int n, m;

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

        pushup(u);
    }
    //int mid = tr[u].l + tr[u].r >> 1;
}
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 mid = l + 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);
        pushup(u);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while (cin >> n >> m)
    {
        for (int i = 1; i <= n; i++)
            cin >> arr[i];

        build(1, 1, n);

        for (int i = 0; i < m; i++)
        {
            string s;
            int l, r;
            cin >> s >> l >> r;
            if (s == "Q")
            {
                cout << query(1, l, r) << endl;
            }
            else if (s == "U")
            {
                modify(1, l, r);
            }
        }
    }
    return 0;
}

C A Simple Problem with Integers(POJ3468)

题目大意:
给出一个序列,进行两个操作,第一个是a到b区间每个数加上c,第二个是求a到b的和。
思路:
直接上线段树就行,维护一个区间和,一个懒标记。
或者可以使用树状数组来写(因为有点麻烦稍微,就没有写)线段树不用动脑子,所以就用线段树了
如果使用树状数组的话,就是用差分数组,然后推导一些求和时候的公式就可以了。
给出线段树的AC代码。
AC代码

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
int n, q;
struct Node
{
    int l, r;
    ll sum, add;
} tr[max_n * 4];

int arr[max_n];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
    if (tr[u].add)
    {
        tr[u << 1].add += tr[u].add;
        tr[u << 1 | 1].add += tr[u].add;

        tr[u << 1].sum += (ll)(tr[u << 1].r - tr[u << 1].l + 1) * tr[u].add;
        tr[u << 1 | 1].sum += (ll)(tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * tr[u].add;

        tr[u].add = 0;
    }
}
void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, r, arr[r], 0};
    }
    else
    {
        tr[u] = {l, r};
        int mid = tr[u].l + tr[u].r >> 1;

        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);

        pushup(u);
    }
}

void modify(int u, int l, int r, int x)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum += (tr[u].r - tr[u].l + 1) * x;
        tr[u].add += x;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;

        if (l <= mid)
            modify(u << 1, l, r, x);
        if (r > mid)
            modify(u << 1 | 1, l, r, x);
        pushup(u);
    }
}
ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].sum;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;

        ll ans = 0;
        if (l <= mid)
            ans = query(u << 1, l, r);
        if (r > mid)
            ans += query(u << 1 | 1, l, r);
        return ans;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> arr[i];
    build(1, 1, n);

    //for (int u = 1; u <= 25; u++)
    //    cout << tr[u].l << " " << tr[u].r << " " << tr[u].sum << endl;

    string s;
    int l, r;
    for (int i = 1; i <= q; i++)
    {

        cin >> s >> l >> r;
        if (s == "Q")
        {
            cout << query(1, l, r) << endl;
        }
        else if (s == "C")
        {
            int x;
            cin >> x;
            modify(1, l, r, x);
        }
    }

    return 0;
}

D Mayor’s posters(POJ2528)

题目大意:
给出n个l和r,每个区间都会覆盖上一种颜色,求所有的覆盖完之后,能有多少种可见的颜色。
思路:
使用线段树维护每一段的颜色情况,不可见置为0,最后直接查询整个树看有多少种就行了。
另外需要注意的是,因为数据范围l和r较大,那么应该进行离散化处理。详细可见代码。
AC代码

#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <string>
#include <cstdlib>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
pair<int, int> pii[max_n];
vector<int> vi;
int arr[max_n];
map<int, int> mp;

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

void pushdown(int u)
{
    tr[u << 1].v = tr[u].v;
    tr[u << 1 | 1].v = tr[u].v;
    tr[u].v = 0; //父节点被覆盖
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0};
    //cout << tr[u].l << " " << tr[u].r << " " << tr[u].v << endl;
    if (l == r)
        return;
    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

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

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

    //if (tr[u].l == 1 && tr[u].r == 1)
    //    cout << "1" << endl;

    if (tr[u].v)
    {
        //cout << tr[u].l << " " << tr[u].r << " " << tr[u].v << endl;
        mp[tr[u].v] = 1;
        return;
    }

    int mid = tr[u].l + tr[u].r >> 1;

    if (l <= mid)
        query(u << 1, l, r);
    if (r > mid)
        query(u << 1 | 1, l, r);
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        mp.clear();
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> pii[i].first >> pii[i].second;
            vi.push_back(pii[i].first);
            vi.push_back(pii[i].second);
        }

        sort(vi.begin(), vi.end());
        vi.erase(unique(vi.begin(), vi.end()), vi.end());

        arr[0] = 1;
        int index = 1;

        for (int i = 1; i < vi.size(); i++)
        {
            if (vi[i] - vi[i - 1] == 1) //如果原本就是相邻的,那么把长度设为1,反之为2
                index++;
            else
                index += 2;

            arr[i] = index;
        }

        for (int i = 0; i < n; i++)
        {
            pii[i].first = arr[lower_bound(vi.begin(), vi.end(), pii[i].first) - vi.begin()];
            pii[i].second = arr[lower_bound(vi.begin(), vi.end(), pii[i].second) - vi.begin()];
        }

        build(1, 1, vi.size() * 2);

        for (int i = 0; i < n; i++)
        {
            //    cout << pii[i].first << " " << pii[i].second << " " << i + 1 << endl;
            modify(1, pii[i].first, pii[i].second, i + 1);
        }

        // for (int i = 1; i <= 32; i++)
        //     cout << tr[i].l << " " << tr[i].r << " " << tr[i].v << endl;

        query(1, 1, vi.size() * 2);

        cout << mp.size() << endl;
    }

    return 0;
}

E Just a Hook(HDU1698)

题目大意:
一段长度为n的序列,原本所有的值都是1,现在给出l,r把区间内的值改成c,求区间总和。
思路:
线段树维护一个区间和就可以了。和C题相似,只不过一个是在原值的基础上加或者减,这一个是直接修改。

AC代码

#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <cstdlib>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
int n, q;
struct Node
{
    int l, r;
    int sum, add; //区间和 根节点的值
} tr[max_n * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u)
{
    if (tr[u].add)
    {
        tr[u << 1].add = tr[u].add;
        tr[u << 1 | 1].add = tr[u].add;

        tr[u << 1].sum = (tr[u << 1].r - tr[u << 1].l + 1) * tr[u].add;
        tr[u << 1 | 1].sum = (tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1) * tr[u].add;

        tr[u].add = 0;
    }
}
void build(int u, int l, int r)
{

    tr[u] = {l, r, r - l + 1, 0};
    if (l == r)
        return;

    int mid = tr[u].l + tr[u].r >> 1;

    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
}

void modify(int u, int l, int r, int x)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].sum = (tr[u].r - tr[u].l + 1) * x;
        tr[u].add = x;
    }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;

        if (l <= mid)
            modify(u << 1, l, r, x);
        if (r > mid)
            modify(u << 1 | 1, l, r, x);

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

        int ans = 0;
        if (l <= mid)
            ans = query(u << 1, l, r);
        if (r > mid)
            ans += query(u << 1 | 1, l, r);
        return ans;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int T;
    cin >> T;
    for (int k = 1; k <= T; k++)
    {
        cin >> n >> q;

        build(1, 1, n);

        for (int i = 0; i < q; i++)
        {
            int l, r, x;
            cin >> l >> r >> x;
            modify(1, l, r, x);
        }

        cout << "Case " << k << ": The total value of the hook is " << query(1, 1, n) << "." << endl;
    }

    return 0;
}

F Balanced Lineup(POJ3264)

题目大意:
给出长度为n的序列,然后对于每次询问l和r,输出区间中最大值和最小值的差。
思路:
线段树维护一个最小值和一个最大值,因为不用修改所以不用modify。写两个query函数,一个用来查询最小值,一个用来查询最大值,然后输出他们的差值就行了。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 5e4 + 100;
int arr[max_n];
int n, q;
struct Node
{
    int l, r;
    int ma, mi;
} tr[max_n * 4];

void pushup(int u)
{
    tr[u].ma = max(tr[u << 1].ma, tr[u << 1 | 1].ma);
    tr[u].mi = min(tr[u << 1].mi, tr[u << 1 | 1].mi);
}
void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, r, arr[r], arr[r]};
    }
    else
    {
        tr[u] = {l, r};
        int mid = tr[u].l + tr[u].r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}
int query1(int u, int l, int r) //查询区间最大值
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].ma;

    int mid = tr[u].l + tr[u].r >> 1;
    int ans = 0;
    if (l <= mid) //包含左区间
        ans = query1(u << 1, l, r);
    if (r > mid) //包含右区间
        ans = max(ans, query1(u << 1 | 1, l, r));
    return ans;
}
int query2(int u, int l, int r) //查询区间最小值
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].mi;

    int mid = tr[u].l + tr[u].r >> 1;
    int ans = 1 << 30;
    if (l <= mid)
        ans = query2(u << 1, l, r);
    if (r > mid)
        ans = min(ans, query2(u << 1 | 1, l, r));
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> arr[i];

    build(1, 1, n);

    for (int i = 0; i < q; i++)
    {
        int l, r;
        cin >> l >> r;
        cout << query1(1, l, r) - query2(1, l, r) << endl;
    }
    return 0;
}

G Can you answer these queries?(HDU4207)

题目大意:
给出长度为n的序列,然后给出若干个修改和查询,每次修改是将l,r内所有数开平方,查询l,r的区间和。
思路:
本身题目并不是很难写,但是坑点非常多。
1、要使用long long,然后每个例子输出完之后要有一个空格。
2、没有说l和r的大小关系,要加判断(非常非常坑)
3、(优化的地方)这个题如果直接一个个开方会超时,意识到1开方之后还是1,那么就可以判断一下区间中的数是不是全是1,如果全是1那么可以直接return了。

AC代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
ll arr[max_n];
struct Node
{
    int l, r;
    ll sum;
} tr[max_n * 4];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, r, arr[r]};
    }
    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 l, int r)
{
    if (tr[u].l == tr[u].r)
    {
        tr[u].sum = sqrt(tr[u].sum);
        return;
    }

    if (tr[u].l >= l && tr[u].r <= r && tr[u].sum == (ll)(tr[u].r - tr[u].l + 1))
        return;

    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid)
        modify(u << 1, l, r);
    if (r > mid)
        modify(u << 1 | 1, l, r);
    pushup(u);
}
ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
        return tr[u].sum;

    int mid = tr[u].l + tr[u].r >> 1;
    ll ans = 0;

    if (l <= mid)
        ans = query(u << 1, l, r);
    if (r > mid)
        ans += query(u << 1 | 1, l, r);

    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n;
    int index = 1;
    while (cin >> n)
    {
        cout << "Case #" << index++ << ":" << endl;
        for (int i = 1; i <= n; i++)
            cin >> arr[i];

        build(1, 1, n);

        int q;
        cin >> q;
        while (q--)
        {
            int x, l, r;
            cin >> x >> l >> r;

            if (l > r)
                swap(l, r);

            if (x == 0)
            {
                modify(1, l, r);
            }
            else if (x == 1)
            {
                cout << query(1, l, r) << endl;
            }
        }
        cout << endl;
    }

    return 0;
}

H Tunnel Warfare(POJ2892)

题目大意:
给出一个长度为n的数列,状态有1或者0,1代表连着,0代表断开(刚开始全是1),给出3种操作,D摧毁第x个位置(即1变成0)R撤销上一次的摧毁操作,Q查询包含这个位置的最大连续区间长度。
思路:
可以使用线段树维护两个值,一个是左边的最大串长度lmax,一个是右边的最大串长度rmax。查询的时候看哪边包含了这个节点,如果左子树的rmax包含了这个节点,那么应该返回左子树的rmax+右子树的lmax(画图一目了然)
细节写到注释里面了。
另外摧毁操作使用栈去维护会很好处理。
AC代码

#include <iostream>
#include <algorithm>
#include <stack>

using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;

struct Node
{
    int l, r;
    int lmax, rmax; //左最大串长度,右最大串长度

} tr[max_n * 4];

void pushup(int u)
{
    //父节点的lmax应该是左子节点的lmax
    //如果左子节点的lmax和区间长度一样,那么要加上右子节点的lmax
    //rmax同理
    tr[u].lmax = tr[u << 1].lmax;
    if (tr[u << 1].lmax == tr[u << 1].r - tr[u << 1].l + 1)
        tr[u].lmax += tr[u << 1 | 1].lmax;

    tr[u].rmax = tr[u << 1 | 1].rmax;
    if (tr[u << 1 | 1].rmax == tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)
        tr[u].rmax += tr[u << 1].rmax;
}
void build(int u, int l, int r)
{

    tr[u] = {l, r, 1, 1};
    if (l == r)
        return;

    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 a, int x)
{
    if (tr[u].l == tr[u].r)
    {
        tr[u].lmax = x;
        tr[u].rmax = x;
    }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (a <= mid)
            modify(u << 1, a, x);
        else if (a > mid)
            modify(u << 1 | 1, a, x);

        pushup(u);
    }
}

int query(int u, int a)
{
    if (tr[u].l == tr[u].r)
        return tr[u].lmax;

    int mid = tr[u].l + tr[u].r >> 1;
    if (a <= mid)
    {
        if (a >= tr[u << 1].r - tr[u << 1].rmax + 1)      //左子树的右最大包含了目标节点
            return tr[u << 1].rmax + tr[u << 1 | 1].lmax; //返回左子树右最大+右子树左最大 下同
        return query(u << 1, a);
    }
    else
    {
        if (a <= tr[u << 1 | 1].l + tr[u << 1 | 1].lmax - 1)
            return tr[u << 1].rmax + tr[u << 1 | 1].lmax;
        return query(u << 1 | 1, a);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    while (cin >> n >> q)
    {
        build(1, 1, n);
        //cout << "------" << endl;

        stack<int> st; //恢复节点
        while (q--)
        {
            string s;
            int a;
            cin >> s;
            if (s == "D")
            {
                cin >> a;
                modify(1, a, 0);
                st.push(a);
            }
            else if (s == "Q")
            {
                cin >> a;
                cout << query(1, a) << endl;
            }
            else
            {
                a = st.top();
                modify(1, a, 1);
                st.pop();
            }
        }
    }
    return 0;
}

I Array Partition(CF1454F)

题目大意:
枚举两个点把区间分成3部分,使得第一部分的最大值等于中间一部分的最小值等于最后一部分的最大值。输出任何一个满足题意的解即可。
思路:
使用线段树维护区间的最大值和最小值。可以枚举第一个点,然后二分第二个点。
AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
int arr[max_n];

struct Node
{
    int l, r;
    int ma, mi;
} tr[max_n * 4];

void pushup(int u)
{
    tr[u].ma = max(tr[u << 1].ma, tr[u << 1 | 1].ma);
    tr[u].mi = min(tr[u << 1].mi, tr[u << 1 | 1].mi);
}
void build(int u, int l, int r)
{
    if (l == r)
    {
        tr[u] = {l, r, arr[r], arr[r]};
    }
    else
    {
        tr[u] = {l, r, 0, 0};
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

int query1(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        return tr[u].ma;
    }
    int mid = tr[u].l + tr[u].r >> 1;

    int ans = 0;
    if (l <= mid)
        ans = query1(u << 1, l, r);
    if (r > mid)
        ans = max(ans, query1(u << 1 | 1, l, r));
    return ans;
}

int query2(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        return tr[u].mi;
    }
    int mid = tr[u].l + tr[u].r >> 1;

    int ans = 1 << 30;
    if (l <= mid)
        ans = query2(u << 1, l, r);
    if (r > mid)
        ans = min(ans, query2(u << 1 | 1, l, r));
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> arr[i];
        build(1, 1, n);

        bool isok = false;
        for (int i = 1; i < n - 1; i++)
        {
            int ans = query1(1, 1, i); //值
            //二分第二个点
            int l = i + 1;
            int r = n - 1;
            while (l <= r)
            {
                int j = l + r >> 1;
                int x = query2(1, i + 1, j);
                int y = query1(1, j + 1, n);
                if (ans < x || ans < y)
                    l = j + 1;
                else if (ans > x || ans > y)
                    r = j - 1;
                else
                {
                    cout << "YES" << endl;
                    cout << i << " " << j - i << " " << n - j << endl;
                    isok = true;
                    break;
                }
            }
            if (isok)
                break;
        }
        if (!isok)
        {
            cout << "NO" << endl;
        }
    }

    return 0;
}

J A Simple Task (CF558E)

题目大意:
给一个字符串,每次给出l和r让非升序排序或者非降序排序。最后输出结果。
思路:
sort
这个题看了好久,然后看了好多大佬的思路,然后才明白了一点点。我们可以建立26棵线段树,这样每棵线段树维护一个字母。线段树里面维护一下区间和就可以,每次排序相当于区间修改。最后暴力输出一下结果就可以了。
代码种注释部分写的比较详细。
另,26棵线段树在创建和维护的时候都可以一颗一颗的来,只要加一个参数就可以,所以就按照维护一棵线段树的方式去写就行了。

AC代码

#include <bits/stdc++.h>
using namespace std;

#define ll long long
#define endl "\n"
const int max_n = 1e5 + 100;
/*
假设区间需要从小到大排序,那么先从a开始,找到a的数量,然后把该区间内的原有a清空,
然后从区间左端开始平铺答案数个a。
以此类推,找到b的数量,清空,平铺在a的右边。从大到小同理。
26棵线段树 维护26个英文字母
*/
struct Node
{
    int l, r;
    int v, lazy = -1; //v区间和(即l到r中字母的数量)
} tr[30][max_n * 4];
string s;
int arr[30][max_n];

void pushdown(int u, int k)
{
    //如果不用传递懒标记
    if (tr[k][u].lazy == -1)
        return;
    //左子树的和=左子树的区间长度 * 懒标记
    tr[k][u << 1].v = (tr[k][u << 1].r - tr[k][u << 1].l + 1) * tr[k][u].lazy;
    tr[k][u << 1 | 1].v = (tr[k][u << 1 | 1].r - tr[k][u << 1 | 1].l + 1) * tr[k][u].lazy;
    //左子树的懒标记 = 父节点的懒标记
    tr[k][u << 1].lazy = tr[k][u].lazy;
    tr[k][u << 1 | 1].lazy = tr[k][u].lazy;
    //清空父节点的懒标记
    tr[k][u].lazy = -1;
}
void pushup(int u, int k)
{
    tr[k][u].v = tr[k][u << 1].v + tr[k][u << 1 | 1].v;
}
void build(int u, int k, int l, int r)
{
    if (l == r)
    {
        tr[k][u] = {l, r, arr[k][r], -1};
    }
    else
    {
        tr[k][u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, k, l, mid);
        build(u << 1 | 1, k, mid + 1, r);

        pushup(u, k);
    }
}
void modify(int u, int k, int l, int r, int x)
{
    if (tr[k][u].l >= l && tr[k][u].r <= r)
    {
        tr[k][u].lazy = x;
        tr[k][u].v = (tr[k][u].r - tr[k][u].l + 1) * x;
    }
    else
    {
        pushdown(u, k);

        int mid = tr[k][u].l + tr[k][u].r >> 1;
        if (l <= mid)
            modify(u << 1, k, l, r, x);
        if (r > mid)
            modify(u << 1 | 1, k, l, r, x);

        pushup(u, k);
    }
}
int query(int u, int k, int l, int r)
{
    if (tr[k][u].l >= l && tr[k][u].r <= r)
        return tr[k][u].v;

    pushdown(u, k);

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

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    string tmp;
    s += "1"; //同步起点
    cin >> tmp;
    s += tmp;
    for (int i = 1; i <= n; i++)
        arr[s[i] - 'a'][i] = 1;

    for (int i = 0; i < 26; i++)
        build(1, i, 1, n);
    while (q--)
    {
        int l, r, c;
        cin >> l >> r >> c;
        if (c == 0)
        {
            int cnt = 0;
            for (int i = 25; i >= 0; i--)
            {
                //第一步:获取区间内字母的数量x
                int x = query(1, i, l, r);
                //如果字母个数为0 continue
                if (x == 0)
                    continue;
                //第二步:修改区间所有值为0
                modify(1, i, l, r, 0);
                //第三步:修改区间中间 x 位为1  [l + cnt,l + cnt + x - 1]
                modify(1, i, l + cnt, l + cnt + x - 1, 1);

                //第四步,更新cnt的值
                cnt += x;
            }
        }
        else
        {
            //已经排好了多少位
            int cnt = 0;
            for (int i = 0; i < 26; i++)
            {
                //第一步:获取区间内字母的数量x
                int x = query(1, i, l, r);
                //如果字母个数为0 continue
                if (x == 0)
                    continue;
                //第二步:修改区间所有值为0
                modify(1, i, l, r, 0);
                //第三步:修改区间中间 x 位为1  [l + cnt,l + cnt + x - 1]
                modify(1, i, l + cnt, l + cnt + x - 1, 1);

                //第四步,更新cnt的值
                cnt += x;
            }
            //int qwq = 'v' - 'a'; //10;
            //for (int i = 1; i <= 24; i++)
            //    cout << tr[qwq][i].l << " " << tr[qwq][i].r << " " << tr[qwq][i].v << " " << tr[qwq][i].lazy << endl;
        }
    }
    //直接搜索每一位的字母
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j < 26; j++)
        {
            if (query(1, j, i, i) == 1)
            {
                printf("%c", 'a' + j);
                break;
            }
        }
    }
    return 0;
}

总结

打CF自闭了,总结啥都不想写
如果只是板子的那种线段树的话,感觉就非常的简单,但是一旦要套用其他的东西,或者维护的东西不太一样的时候,就要思考很多了,例如最后一个题,建26棵树,还有求最大长度的那个题。继续努力吧。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值