线段树总结

这两天对着HH的blog又把线段树的题目翻出来做做。

HHBlog地址:HH神牛的线段树讲解

决定将自己线段的风格改成HH那种,不要新开结构体。 然后PushDown, Pushup函数就变成了重要模块。

一般情况下约定:

PushUP(int rt)是把当前结点的信息更新到父结点

PushDown(int rt)是把当前结点的信息更新给儿子结点

rt表示当前子树的根(root),也就是当前所在的结点

update表示对线段树更新的函数

query表示对线段树进行查询的函数


这样重点被抽分出来很清晰,就是如何设计这几个函数。

线段树作为一种重要信息统计数据结构, 对付一个较大区间的动态变化, 能够在复杂度很低的情况下给出快速的统计。

近期主要总结的是线段树的基本应用范围, 后面也会接触一些比较综合的运用, 所以这篇博客将持续更新


单点更新:最简单的线段树分类, 每次只更新一个叶子节点。



成段更新:每次更新一个区间的值,这里有一种很重要的思想: Lazy标记,或者叫做延迟更新。

这种思想就是能够对某一段进行整体操作的时候就先进行一个整体的信息保存, 等到真正需要细分这一区间的信息的时候再根据标记去把信息传递下去,这里的操作很灵活,是设计的难点。

POJ1436

一道简单的成段更新的题目。
题意:给一个二维坐标下有一些线段。 已知每条线段的y1, y2, x分贝代表线段的y轴的起始点 以及在X轴的位置。现在要求这样的三元组:(x,y,z)使得这三个点之间两两互为可见。 x, y两点互为可见是指; 存在一条平行于X州的的线段可以连接x,y且这条线段不会与任何其他的线段有交点。
分析:首先将所有的线段按照X轴的位置从小到大排序。从左到右扫描, 判断当前线段的区间,在之间的线段覆盖之后各段都是由哪条线段覆盖的。 好比从当前的线段发出一束光(平行光), 射向左边, 看哪些线段被光照到了。同时Y能照到X, X肯定可以照到Y。
注意的是在处理过程中要考虑这样一种情况:比如区间[1,2] [3,5]可能直接按线段树处理的话区间[1,5]会被处理, 但实际上[2,3]这一段是空的。处理的办法是:将所有的点的纵坐标的值都*2, 这样就可以将线段的端点之间区分开。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 8010;
bool vis[N][N];
int cov[N<<3];
struct P{
    int x, y1, y2;
    P() {};
}a[N];

bool cmp(P a, P b){
    return a.x < b.x;
}

void PushDown(int rt){
    if(cov[rt] != -1){
        cov[rt<<1] = cov[rt<<1|1] = cov[rt];
        cov[rt] = -1;
    }
}

void update(int L, int R, int rt, int l, int r, int c){
    if(L <= l && R >= r){
        cov[rt] = c;
        return ;
    }
    PushDown(rt);
    int m = (l + r) >> 1;
    if(L <= m) update(L, R, rt<<1, l, m, c);
    if(R > m) update(L, R, rt<<1|1, m + 1, r, c);
}

void query(int L, int R, int rt, int l, int r, int c){
    if(cov[rt] != -1){
        vis[cov[rt]][c] = 1;
        return ;
    }
    if(l == r) return ;
    int m = (l + r) >> 1;
    if(L <= m) query(L, R, rt<<1, l, m, c);
    if(R > m) query(L, R, rt<<1|1, m + 1, r, c);
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int n;
        scanf("%d", &n);
        for(int i = 0; i < n; ++i){
            scanf("%d%d%d", &a[i].y1, &a[i].y2, &a[i].x);
        }
        sort(a, a + n, cmp);
        memset(cov, -1, sizeof(cov));
        memset(vis, 0, sizeof(vis));
        for(int i = 0; i < n; ++i){
            query(a[i].y1<<1, a[i].y2<<1, 1, 1, N<<1, i);
            update(a[i].y1<<1, a[i].y2<<1, 1, 1, N<<1, i);
        }

        int ans = 0;
        for(int i = 0; i < n; ++i){
            for(int j = 0; j < n; ++j){
                if(vis[i][j]){
                    for(int k = 0; k < n; ++k){
                        if(vis[i][k] && vis[j][k])
                            ++ans;
                    }
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}


POJ3225

这是一道非常好的成段更新的题目, 用到了延迟标记, 而且具有一定的逻辑性。我参考了HH神牛的代码才完成(悲剧的是几乎变成写的一样的代码)。

题意: 给出几个常见的操作的定义, 求初始为空的区间经过给定的操作之后区间的情况。

分析: 首先了解几种操作的含义:

    U:把区间[l,r]覆盖成1
    I:把[-∞,l)(r,∞]覆盖成0
    D:把区间[l,r]覆盖成0
    C:把[-∞,l)(r,∞]覆盖成0 , 且[l,r]区间0/1互换
    S:[l,r]区间0/1互换

 cov(1,0,-1),其中0,1表示覆盖0或者1, -1表示没有lazy标记,   xor(0,1)表示异或操作。   比较简单的操作是线段覆盖(cov),一旦遇到覆盖肯定就被覆盖了, xor此时一定为0(没有异或关系)。

如果是xor操作, 那么如果此时cov值不等于-1即这一段的值统一标记,那我可以直接改变这一段区间的值取反即可, 否则就只能去改变它的xor标记了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 65536*2 + 10;
int Xor[N << 2];
int cov[N << 2];
bool vis[N];

void PXor(int rt){
    if(cov[rt] != -1){
        cov[rt] ^= 1;
    }
    else{
        Xor[rt] ^= 1;
    }
}

void PushDown(int rt){
    if(cov[rt] != -1){
        cov[rt<<1] = cov[rt<<1|1] = cov[rt];
        Xor[rt<<1] = Xor[rt<<1|1] = 0;
        cov[rt] = -1;
    }
    if(Xor[rt]){
        PXor(rt<<1);
        PXor(rt<<1|1);
        Xor[rt] = 0;
    }
}

void update(char op, int L, int R, int l, int r, int rt){
    if(L <= l && R >= r){
        if(op == 'U'){
            cov[rt] = 1;
            Xor[rt] = 0;
        }
        else if(op == 'D'){
            cov[rt] = 0;
            Xor[rt] = 0;
        }
        else if(op == 'C' || op == 'S'){
            PXor(rt);
        }
        return ;
    }

    PushDown(rt);
    int m = (l + r) >> 1;
    if(L <= m) update(op, L, R, l, m, rt<<1);
    else if(op == 'I' || op == 'C'){
        cov[rt<<1] = Xor[rt<<1] = 0;
    }
    if(m < R) update(op, L, R, m + 1, r, rt<<1|1);
    else if(op == 'I' || op == 'C'){
        cov[rt<<1|1] = Xor[rt<<1|1] = 0;
    }
}

void query(int l, int r, int rt){
    if(cov[rt] == 1){
        for(int i = l; i <= r; ++i){
            vis[i] = 1;
        }
        return;
    }
    else if(cov[rt] == 0){
        return ;
    }
    PushDown(rt);
    int m = (l + r) >> 1;
    query(l, m, rt<<1);
    query(m + 1, r, rt <<1|1);
}

int main(){
    char op , l , r;
	int a , b;
	while ( ~scanf("%c %c%d,%d%c\n",&op , &l , &a , &b , &r) ) {
		a <<= 1 , b <<= 1;
		if (l == '(') a ++;
		if (r == ')') b --;
		if (a > b) {
			if (op == 'C' || op == 'I') {
				cov[1] = Xor[1] = 0;
			}
		} else update(op, a, b, 0, N - 1, 1);
	}

    query(0, N - 1, 1);
    bool flag = 0;
    int s = -1, e;
    for(int i = 0; i < N; ++i){
        if(vis[i]){
            if(s == -1){
                s = i;
            }
            e = i;
        }
        else{
            if(s != -1){
                if(flag) printf(" ");
                flag = 1;
                printf("%c%d,%d%c", s&1 ? '(' : '[', s >> 1, (e + 1) >> 1, e&1 ? ')' : ']');
                s = -1;
            }
        }
    }
    if (!flag) printf("empty set");
    printf("\n");
    return 0;
}


区间合并:区间合并经常会要求在update的时候, 要向上更新PushUp.

POJ3667

这是一道典型的区间合并的题目, 同时也用到了成段更新。

题意:给出一个区间出事为空, 有两种操作:1 是给出长度为a的线段,问有没有区间可以放得下, 如果有的话 尽量往左放。 2 是给出区间[a, b]将这一段区间清空。

分析:因为有可以将区间清空或者填满的操作, 在操作过程中会涉及到区间的合并。

设定三个数组分别lsum[rt], rsum[rt], msum[rt]分别表示区间中以左端点开始的最长连续空间, 以右端点开始的最长连续空间, 区间最长的连续空间。

其中lsum, rsum 是为了区间合并时提供足够的信息。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxab(a,b) ((a) > (b) ? (a) : (b))
using namespace std;
const int N = 50010;
int lsum[N<<2], rsum[N<<2], msum[N<<2];
int cov[N<<2];

void build(int rt, int l, int r){
    lsum[rt] = rsum[rt] = msum[rt] = (r - l + 1);
    cov[rt] = -1;
    if(l != r){
        int m = (l + r) >> 1;
        build(rt<<1, l, m);
        build(rt<<1|1, m + 1, r);
    }
}

void PushDown(int rt, int m){
    if(cov[rt] != -1){
        cov[rt<<1] = cov[rt<<1|1] = cov[rt];
        if(cov[rt] == 0){
            lsum[rt<<1] = rsum[rt<<1]= msum[rt<<1] = m - (m>>1);
            lsum[rt<<1|1] = rsum[rt<<1|1] = msum[rt<<1|1] = m>>1;
        }
        else{
            lsum[rt<<1] = rsum[rt<<1] = 0;
            lsum[rt<<1|1] = rsum[rt<<1|1] = 0;
            msum[rt<<1|1] = msum[rt<<1] = 0;
        }
        cov[rt] = -1;
    }
}

void PushUp(int rt, int m){
    lsum[rt] = lsum[rt<<1];
    rsum[rt] = rsum[rt<<1|1];
    if(lsum[rt] == m - (m>>1)) lsum[rt] += lsum[rt<<1|1];
    if(rsum[rt] == m>>1) rsum[rt] += rsum[rt<<1];
    msum[rt] = maxab(rsum[rt<<1] + lsum[rt<<1|1], maxab(msum[rt<<1], msum[rt<<1|1]));
}

void update(int L, int R, int rt, int l, int r, int c){
    if(L <= l && R >= r){
        lsum[rt] = rsum[rt] = msum[rt] = c ? 0: (r - l + 1);
        cov[rt] = c;
        return ;
    }
    PushDown(rt, r - l + 1);
    int m = (l + r) >> 1;
    if(L <= m) update(L, R, rt<<1, l, m, c);
    if(R > m) update(L, R, rt<<1|1, m + 1, r, c);
    PushUp(rt, r - l + 1);
}

int query(int rt, int l, int r, int x){
    if(l == r) return l;
    PushDown(rt, r - l + 1);
    int m = (l + r) >> 1;
    if(msum[rt<<1] >= x) return query(rt<<1, l, m, x);
    else if(rsum[rt<<1] + lsum[rt<<1|1] >= x) return m - rsum[rt<<1] + 1;
    else return query(rt<<1|1, m + 1, r, x);
}

int main(){
    int n, m;
    scanf("%d%d", &m, &n);
    build(1, 1, m);
    while(n--){
        int op, a, b;
        scanf("%d", &op);
        if(op == 1){
            scanf("%d", &a);
            if(a > msum[1]) printf("0\n");
            else{
                int p = query(1, 1, m, a);
                printf("%d\n", p);
                update(p, p + a - 1, 1, 1, m, 1);
            }
        }
        else{
            scanf("%d%d", &a, &b);
            update(a, a + b - 1, 1, 1, m, 0);
        }
    }
    return 0;
}



资源下载链接为: https://pan.quark.cn/s/3d8e22c21839 随着 Web UI 框架(如 EasyUI、JqueryUI、Ext、DWZ 等)的不断发展与成熟,系统界面的统一化设计逐渐成为可能,同时代码生成器也能够生成符合统一规范的界面。在这种背景下,“代码生成 + 手工合并”的半智能开发模式正逐渐成为新的开发趋势。通过代码生成器,单表数据模型以及一对多数据模型的增删改查功能可以被直接生成并投入使用,这能够有效节省大约 80% 的开发工作量,从而显著提升开发效率。 JEECG(J2EE Code Generation)是一款基于代码生成器的智能开发平台。它引领了一种全新的开发模式,即从在线编码(Online Coding)到代码生成器生成代码,再到手工合并(Merge)的智能开发流程。该平台能够帮助开发者解决 Java 项目中大约 90% 的重复性工作,让开发者可以将更多的精力集中在业务逻辑的实现上。它不仅能够快速提高开发效率,帮助公司节省大量的人力成本,同时也保持了开发的灵活性。 JEECG 的核心宗旨是:对于简单的功能,可以通过在线编码配置来实现;对于复杂的功能,则利用代码生成器生成代码后,再进行手工合并;对于复杂的流程业务,采用表单自定义的方式进行处理,而业务流程则通过工作流来实现,并且可以扩展出任务接口,供开发者编写具体的业务逻辑。通过这种方式,JEECG 实现了流程任务节点和任务接口的灵活配置,既保证了开发的高效性,又兼顾了项目的灵活性和可扩展性。
资源下载链接为: https://pan.quark.cn/s/502b0f9d0e26 “vue后台管理前后端代码.zip”项目是一个完整的后台管理系统实现,包含前端、后端和数据库部分,适合新手学习。前端方面,Vue.js作为核心视图层框架,凭借响应式数据绑定和组件化功能,让界面构建与用户交互处理更高效。Element UI作为基于Vue的开源组件库,提供了丰富的企业级UI组件,如表格、按钮、表单等,助力快速搭建后台管理界面。项目还可能集成了Quill、TinyMCE等富文本编辑器,方便用户进行内容编辑。 后端采用前后端分离架构,前端负责数据展示和交互,后端专注于业务逻辑和数据处理,提升了代码的模块化程度、维护可性和可扩展性。后端部分可能涉及使用Node.js(如Express或Koa框架)或其他后端语言(如Java、Python)编写服务器端API接口,用于接收前端请求、处理数据并返回响应。 数据库使用MySQL存储数据,如用户信息、商品信息、订单等,开发者通过SQL语句进行数据的增删改查操作。 通过学习该项目,初学者可以掌握以下要点:Vue.js的基础知识,包括基本语法、组件化开发、指令、计算属性、监听器等;Element UI的引入、配置及组件使用方法;前后端通信技术,如AJAX或Fetch API,用于前端请求后端数据;RESTful API的设计原则,确保后端接口清晰易用;数据库表结构设计及SQL查询语句编写;基本的认证与授权机制(如JWT或OAuth),保障系统安全;以及前端和后端错误处理与调试技巧。 这个项目为初学者提供了一个全面了解后台管理系统运作的实践平台,覆盖从前端交互到后端处理再到数据存储的全过程。在实践中,学习者不仅能巩固理论知识,还能锻炼解决实际问题的能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值