【线段树】的一些神奇操作

openjudge上面cdqz的数据结构22题非常良心啊!!!(虽然太菜了只做了几道。。
然后对线段树有了一些更多的理解

(为什么1004提交不起aaaa很想知道有没有把指针写对qwq

1002:Challenge 2

查看 提交 统计 提问
总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 262144kB
描述
给一个空数列,有M次操作,每次操作是以下三种之一:

(1)在数列后加一个数

(2)求数列中某位置的值

(3)撤销掉最后进行的若干次操作(1和3)

输入
第一行一个正整数M。
接下来M行,每行开头是一个字符,若该字符为’A’,则表示一个加数操作,接下来一个整数x,表示在数列后加一个整数x;若该字符为’Q’,则表示一个询问操作,接下来一个整数x,表示求x位置的值;若该字符为’U’,则表示一个撤销操作,接下来一个整数x,表示撤销掉最后进行的若干次操作。
输出
对每一个询问操作单独输出一行,表示答案。
样例输入
9
A 1
A 2
A 3
Q 3
U 1
A 4
Q 3
U 2
Q 3
样例输出
3
4
3
提示
1<=M<=10^5,输入保证合法,且所有整数可用带符号32位整型存储。

带撤销操作,直接主席树动态开点,把每个操作的根节点存下来,撤销时返回就可以。【注意】根节点点数是有效操作决定的,询问不计入。
#include<iostream>
#include<cstdio>
using namespace std;

struct node {
    int num;
    node *ls, *rs;
} *zero, pool[100005*32], *tail = pool, *root[100005];

int len[100005];

node * newnode ( ) {
    node * nd = ++ tail;
    nd -> ls = zero;
    nd -> rs = zero;
    nd -> num = 0;
    return nd;
}

node * build ( ) {
    node * nd = newnode ( );
    return nd;
}

node * add ( node * nd, int l, int r, int pos, int a ) {
    node * nnd = newnode ( );
    if ( l == r ) {
        nnd -> num = a;
        return nnd;
    }
    int mid = ( l + r ) >> 1;
    if ( pos <= mid ) {
        nnd -> rs = nd -> rs;
        nnd -> ls = add ( nd -> ls, l, mid, pos, a );
    } else {
        nnd -> ls = nd -> ls;
        nnd -> rs = add ( nd -> rs, mid + 1, r, pos, a );
    }
    return nnd;
}

int query ( node * nd, int l, int r, int pos ) {
    if ( l == r ) return nd -> num;
    int mid = ( l + r ) >> 1;
    if ( pos <= mid ) return query ( nd -> ls, l, mid, pos );
    else return query ( nd -> rs, mid + 1, r, pos );
}

int main ( ) {
    int m;
    scanf ( "%d", &m );
    zero = ++ tail;
    zero -> ls = zero;
    zero -> rs = zero;
    zero -> num = 0;
    int num = 0;
    root[0] = build ( );
    for ( int i = 1; i <= m; i ++ ) {
        char opt; int a;
        scanf ( "\n%c %d", &opt, &a );
        if ( opt == 'A' ) {
            root[num+1] = add ( root[num], 1, m, len[num] + 1, a );//////////线段树区间范围始终是大范围,不能只是一个版本的树的区间
            len[num+1] = len[num] + 1;
            num ++;
        } else if ( opt == 'U' ) {
            root[num+1] = root[num-a];
            len[num+1] = len[num-a];
            num ++;
        } else {
            printf ( "%d\n", query ( root[num], 1,m, a ) );/////////////
        }
    }
    return 0;
}

1006:Challenge 6

查看 提交 统计 提问
总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 262144kB
描述
给一个长为N的数列,有M次操作,每次操作是以下两种之一:

(1)修改数列中的一个数

(2)求数列中有多少个数比它前面的数都大

输入
第一行两个正整数N和M。
第二行N的整数表示这个数列。
接下来M行,每行开头是一个字符,若该字符为’M’,则表示一个修改操作,接下来两个整数x和y,表示把x位置的值修改为y;若该字符为’Q’,则表示一个询问操作,求数列中有多少个数比它前面的数都大。
输出
对每一个询问操作单独输出一行,表示答案。
样例输入
5 3
1 2 3 4 5
Q
M 2 4
Q
样例输出
5
3
提示
1<=N<=10^5,1<=M<=10^5,输入保证合法,且所有整数可用带符号32位整型存储。

这里的更新操作非常奇妙丫,看了本校学长一道相似题的题解(bzoj2957)有所感悟,每次更新往右子树内递归计算贡献。右子树还要分左段和右段判断是否左子树做出了贡献,然后继续递归下去。
#include<iostream>
#include<cstdio>
using namespace std;

struct node {
    int val, num;
} TR[400005];

int n, m, a[100005];

int count ( int nd, int l, int r, int val ) {
    if ( l == r ) return TR[nd].val > val;
    int mid = ( l + r ) >> 1;
    if ( TR[nd<<1].val <= val ) return count ( nd << 1 | 1, mid + 1, r, val );//如果右子树的左段小于左子树val则没有对左子树做出贡献,去递归右段对左子树的贡献
    else return TR[nd].num - TR[nd<<1].num + count ( nd << 1, l, mid, val );///如果有贡献则右段贡献右子树总num-左段num(不能直接写右段num,那是在它内部产生的,没有包含对整个右子树的影响),递归计算左段对左子树贡献
}

void modify ( int nd, int l, int r, int pos, int d ) {
    if ( l == r ) {
        TR[nd].val = d;
        TR[nd].num = 1;
        return ;
    }
    int mid = ( l + r ) >> 1;
    if ( pos <= mid ) modify ( nd << 1, l, mid, pos, d );
    else modify ( nd << 1 | 1, mid + 1, r, pos, d );
    TR[nd].val = max ( TR[nd<<1].val, TR[nd<<1|1].val );
    TR[nd].num = TR[nd<<1].num + count ( nd << 1 | 1, mid + 1, r, TR[nd<<1].val );//进入右子树递归
}

int main ( ) {
    scanf ( "%d%d", &n, &m );
    for ( int i = 1; i <= n; i ++ ) {
        scanf ( "%d", &a[i] );
        modify ( 1, 1, n, i, a[i] );
    }
    for ( int i = 1; i <= m; i ++ ) {
        char opt;
        scanf ( "\n%c", &opt );
        if ( opt == 'Q' ) printf ( "%d\n", TR[1].num );
        else {
            int pos, d;
            scanf ( "%d%d", &pos, &d );
            modify ( 1, 1, n, pos, d );
        }
    }
}

(还是先挂一个毕竟过了样例

1004:Challenge 4

查看 提交 统计 提问
总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 262144kB
描述
给一个长为N的数列,有M次操作,每次操作时以下三种之一:

(1)修改数列中的一个数

(2)求数列中某连续一段所有数的两两乘积的和 mod 1000000007

(3)求数列中某连续一段所有相邻两数乘积的和 mod 1000000007

输入
第一行两个正整数N和M。
第二行N的整数表示这个数列。
接下来M行,每行开头是一个字符,若该字符为’M’,则表示一个修改操作,接下来两个整数x和y,表示把x位置的值修改为y;若该字符为’Q’,则表示一个询问操作,接下来两个整数x和y,表示对[x,y]区间做2号询问;若该字符为’A’,则表示一个询问操作,接下来两个整数x和y,表示对[x,y]区间做3号询问。
输出
对每一个询问操作单独输出一行,表示答案。
样例输入
5 5
1 2 3 4 5
Q 1 5
A 1 5
M 2 7
Q 1 5
A 1 5
样例输出
85
40
150
60
提示
1<=N<=10^5,1<=M<=10^5,输入保证合法,且所有整数可用带符号32位整型存储。

2的更新方法推一下就可以出来,但是查询时需要左子树的sum右子树的sum以及他们各自的答案,用结构体把它们一起存下来返回就可以叻。
更新!!结果是指针池开小叻qwq,可能还有mod的问题。以后可能会转博客园叻突然有点舍不得qwq,就是最后一篇blog了吧。
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;

const int mod = 1000000007;
const int N = 100005;

ll a[N];
int n, m;

struct node {
    node *ls, *rs;
    ll sum, num1, num2, lf, rg;
} pool[N*32], *tail = pool, *zero, *root;

void update ( node *nd, node *ld, node *rd ) {
    nd -> sum = ( ld -> sum + rd -> sum + mod ) % mod;
    nd -> num1 = (  ld -> sum * rd -> sum  % mod + ld -> num1 + rd -> num1 + mod ) % mod;
    nd -> num2 = ( ld -> num2 + rd -> num2 + ld -> rg * rd -> lf % mod + mod ) % mod;
    nd -> lf = ld -> lf % mod; nd -> rg = rd -> rg % mod;
}

node *build ( int l, int r ) {
    node *nd = ++ tail;
    if ( l == r ) {
        ll pp = ( a[l] % mod + mod ) % mod;
        nd -> lf = nd -> rg = pp;
        nd -> sum = pp; nd -> num1 = nd -> num2 = 0;
        return nd;
    }
    int mid = ( l + r ) >> 1;
    nd -> ls = build ( l, mid );
    nd -> rs = build ( mid + 1, r );
    update ( nd, nd -> ls, nd -> rs );
    return nd;
}

node *query ( node *nd, int l, int r, int L, int R ) {
    if ( l >= L && r <= R )
        return nd;
    int mid = ( l + r ) >> 1;
    node *nd1 = ++ tail, *nd2 = ++ tail;
    if ( mid >= L )
        nd1 = query ( nd -> ls, l, mid, L, R );
    if ( mid < R )
        nd2 = query ( nd -> rs, mid + 1, r, L, R );
    node *st = ++ tail;
    update ( st, nd1, nd2 );
    return st;
}

void modify ( node *nd, int l, int r, int pos, ll d ) {
    if ( l == r ) {
        nd -> lf = nd -> rg = d;
        nd -> sum = d;
        return ;
    }
    int mid = ( l + r ) >> 1;
    if ( pos <= mid ) modify ( nd -> ls, l, mid, pos, d );
    else modify ( nd -> rs, mid + 1, r, pos, d );
    update ( nd, nd -> ls, nd -> rs );
}

int main ( ) {
    scanf ( "%d%d", &n, &m );
    for ( int i = 1; i <= n; i ++ )
        scanf ( "%lld", &a[i] );
    root = build ( 1, n );
    for ( int i = 1; i <= m; i ++ ) {
        char opt;
        scanf ( "\n%c", &opt );
        if ( opt == 'Q' ) {
            int l, r;
            scanf ( "%d%d", &l, &r );
            node *ans = query ( root, 1, n, l, r );
            printf ( "%lld\n", ( ans -> num1 + mod ) % mod );
        } else if ( opt == 'A' ) {
            int l, r;
            scanf ( "%d%d", &l, &r );
            node *ans = query ( root, 1, n, l, r );
            printf ( "%lld\n", ( ans -> num2 + mod ) % mod );
        } else {
            int pos; ll r;
            scanf ( "%d%lld", &pos, &r );
            r = ( r + mod ) % mod;
            modify ( root, 1, n, pos, r );
        }
    }
    return 0;
}

后面还有好多道aaaaqwq,慢慢刷!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于计算机专业的学生而言,参加各类比赛能够带来多方面的益处,具体包括但不限于以下几点: 技能提升: 参与比赛促使学生深入学习和掌握计算机领域的专业知识与技能,如编程语言、算法设计、软件工程、网络安全等。 比赛通常涉及实际问题的解决,有助于将理论知识应用于实践中,增强问题解决能力。 实践经验: 大多数比赛都要求参赛者设计并实现解决方案,这提供了宝贵的动手操作机会,有助于积累项目经验。 实践经验对于计算机专业的学生尤为重要,因为雇主往往更青睐有实际项目背景的候选人。 团队合作: 许多比赛鼓励团队协作,这有助于培养学生的团队精神、沟通技巧和领导能力。 团队合作还能促进学生之间的知识共享和思维碰撞,有助于形成更全面的解决方案。 职业发展: 获奖经历可以显著增强简历的吸引力,为求职或继续深造提供有力支持。 某些比赛可能直接与企业合作,提供实习、工作机会或奖学金,为学生的职业生涯打开更多门路。 网络拓展: 比赛是结识同行业人才的好机会,可以帮助学生建立行业联系,这对于未来的职业发展非常重要。 奖金与荣誉: 许多比赛提供奖金或奖品,这不仅能给予学生经济上的奖励,还能增强其成就感和自信心。 荣誉证书或奖状可以证明学生的成就,对个人品牌建设有积极作用。 创新与研究: 参加比赛可以激发学生的创新思维,推动科研项目的开展,有时甚至能促成学术论文的发表。 个人成长: 在准备和参加比赛的过程中,学生将面临压力与挑战,这有助于培养良好的心理素质和抗压能力。 自我挑战和克服困难的经历对个人成长有着深远的影响。 综上所述,参加计算机领域的比赛对于学生来说是一个全面发展的平台,不仅可以提升专业技能,还能增强团队协作、沟通、解决问题的能力,并为未来的职业生涯奠定坚实的基础。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值