ZKW统计的力量-线段树
Java工作日计算工具类

Java⼯作⽇计算⼯具类⼯作⽇计算⼯具类主要功能:传⼊两个⽇期,返回这两个⽇期之间有多少个⼯作⽇。
思路:1. 预先设置好⼀定年份范围内的节假⽇、补休到map⾥。
(这⾥暂时只设置了2017 - 2018年的)2. 将这个年份范围内的每⼀天是否为节假⽇存到数组⾥,以2017-2018为例,两年有365*2=730天,则开⼀个数组boolean workdays[730],⽤于存放这个年份范围内的每⼀天是否为⼯作⽇。
判断⽅法为:遍历这个年份范围内的每⼀天,先从map⾥找是否为节假⽇或补休,找不到则以是否为周末来判断。
3. 使⽤线段树或树状数组处理这个数组,从⽽在O(logN)内求出两个⽇期之间有多少个⼯作⽇。
import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Calendar;import java.util.Date;import java.util.GregorianCalendar;import java.util.HashMap;import java.util.Map;import java.util.Scanner;/*** ⼯作⽇计算⼯具类<br/>* ⽬前仅⽀持2017,2018年** @author Corvey* @Date 2018年11⽉9⽇16:53:52*/public class WorkdayUtils {/** 预设⼯作⽇数据的开始年份 */private static final int START_YEAR = 2017;/** 预设⼯作⽇数据的结束年份 */private static final int END_YEAR = 2018;/** 起始⽇期处理策略 */private static final BoundaryDateHandlingStrategy START_DATE_HANDLING_STRATEGY = date -> {Calendar calendar = Calendar.getInstance();calendar.setTime(date);return calendar.get(Calendar.HOUR_OF_DAY) < 12; // 如果开始时间在中午12点前,则当天也算作⼀天,否则不算};/** 结束⽇期处理策略 */private static final BoundaryDateHandlingStrategy END_DATE_HANDLING_STRATEGY = date -> {return true; // 结束时间⽆论⼏点,都算作1天};/** ⼯作⽇map,true为补休,false为放假 */private static final Map<Integer, Boolean> WORKDAY_MAP = new HashMap<>();private static final SegmentTree SEGMENT_TREE;static {initWorkday(); // 初始化⼯作⽇map// 计算从START_YEAR到END_YEAR⼀共有多少天int totalDays = 0;for (int year = START_YEAR; year <= END_YEAR; ++year) {totalDays += getDaysOfYear(year);}int[] workdayArray = new int[totalDays]; // 将⼯作⽇的数据存⼊到数组Calendar calendar = new GregorianCalendar(START_YEAR, 0, 1);for (int i = 0; i < totalDays; ++i) {// 将⽇期转为yyyyMMdd格式的intint datestamp = calendar.get(Calendar.YEAR) * 10000 + (calendar.get(Calendar.MONTH) + 1) * 100 + calendar.get(Calendar.DAY_OF_MONTH);Boolean isWorkDay = WORKDAY_MAP.get(datestamp);if (isWorkDay != null) { // 如果在⼯作⽇map⾥有记录,则按此判断⼯作⽇workdayArray[i] = isWorkDay ? 1 : 0;} else { // 如果在⼯作⽇map⾥没记录,则按是否为周末判断⼯作⽇int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);workdayArray[i] = (dayOfWeek != Calendar.SATURDAY && dayOfWeek != Calendar.SUNDAY) ? 1 : 0;}calendar.add(Calendar.DAY_OF_YEAR, 1);}SEGMENT_TREE = new SegmentTree(workdayArray); // ⽣成线段树}/*** 计算两个⽇期之间有多少个⼯作⽇<br/>* @param startDate* @param endDate* @return*/public static int howManyWorkday(Date startDate, Date endDate) {if (startDate.after(endDate)) {return howManyWorkday(endDate, startDate);}Calendar startCalendar = Calendar.getInstance();startCalendar.setTime(startDate);int startDays = getDaysAfterStartYear(startCalendar) - 1; // 第⼀天从0开始Calendar endCalendar = Calendar.getInstance();endCalendar.setTime(endDate);int endDays = getDaysAfterStartYear(endCalendar) - 1; // 第⼀天从0开始if (startDays == endDays) { // 如果开始⽇期和结束⽇期在同⼀天的话return isWorkDay(startDate) ? 1 : 0; // 当天为⼯作⽇则返回1天,否则0天}if (!START_DATE_HANDLING_STRATEGY.ifCountAsOneDay(startDate)) { // 根据处理策略,如果开始⽇期不算⼀天的话++startDays; // 起始⽇期向后移⼀天}if (!END_DATE_HANDLING_STRATEGY.ifCountAsOneDay(endDate)) { // 根据处理策略,如果结束⽇期不算⼀天的话--endDays; // 结束⽇期向前移⼀天}return SEGMENT_TREE.query(startDays, endDays);}/*** 是否为⼯作⽇* @param date* @return*/public static boolean isWorkDay(Date date) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);int days = getDaysAfterStartYear(calendar) - 1;return SEGMENT_TREE.query(days, days) == 1;}/*** 计算从开始年份到这个⽇期有多少天* @param calendar* @return*/private static int getDaysAfterStartYear(Calendar calendar) {int year = calendar.get(Calendar.YEAR);if (year < START_YEAR || year > END_YEAR) {throw new IllegalArgumentException(String.format("系统⽬前仅⽀持计算%d年⾄%d年之间的⼯作⽇,⽆法计算%d年!", START_YEAR, END_YEAR, year)); }int days = 0;for (int i=START_YEAR; i<year; ++i) {days += getDaysOfYear(i);}days += calendar.get(Calendar.DAY_OF_YEAR);return days;}/*** 计算该年有⼏天,闰年返回366,平年返回365* @param year* @return*/private static int getDaysOfYear(int year) {return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 ? 366 : 365;}/*** 初始化⼯作⽇Map<br/>* ⽇期格式必须为yyyyMMdd,true为补休,false为放假,如果本来就是周末的节假⽇则不需再设置*/private static void initWorkday() {// ---------------2017------------------WORKDAY_MAP.put(20170102, false);WORKDAY_MAP.put(20170122, true);WORKDAY_MAP.put(20170127, false);WORKDAY_MAP.put(20170130, false);WORKDAY_MAP.put(20170131, false);WORKDAY_MAP.put(20170201, false);WORKDAY_MAP.put(20170202, false);WORKDAY_MAP.put(20170204, true);WORKDAY_MAP.put(20170401, true);WORKDAY_MAP.put(20170403, false);WORKDAY_MAP.put(20170404, false);WORKDAY_MAP.put(20170501, false);WORKDAY_MAP.put(20170527, true);WORKDAY_MAP.put(20170529, false);WORKDAY_MAP.put(20170530, false);WORKDAY_MAP.put(20170930, true);WORKDAY_MAP.put(20171002, false);// ------------------2018----------------WORKDAY_MAP.put(20180101, false);WORKDAY_MAP.put(20180211, true);WORKDAY_MAP.put(20180215, false);WORKDAY_MAP.put(20180216, false);WORKDAY_MAP.put(20180219, false);WORKDAY_MAP.put(20180220, false);WORKDAY_MAP.put(20180221, false);WORKDAY_MAP.put(20180224, true);WORKDAY_MAP.put(20180405, false);WORKDAY_MAP.put(20180406, false);WORKDAY_MAP.put(20180408, true);WORKDAY_MAP.put(20180428, true);WORKDAY_MAP.put(20180430, false);WORKDAY_MAP.put(20180501, false);WORKDAY_MAP.put(20180618, false);WORKDAY_MAP.put(20180924, false);WORKDAY_MAP.put(20180929, true);WORKDAY_MAP.put(20180930, true);WORKDAY_MAP.put(20181001, false);WORKDAY_MAP.put(20181002, false);WORKDAY_MAP.put(20181003, false);WORKDAY_MAP.put(20181004, false);WORKDAY_MAP.put(20181005, false);}/*** 边界⽇期处理策略<br/>* 在计算两个⽇期之间有多少个⼯作⽇时,有的特殊需求是如果开始/结束的⽇期在某个时间之前/后(如中午⼗⼆点前),则不把当天算作⼀天<br/> * 因此特将此逻辑分离出来,各⾃按照不同需求实现该接⼝即可* @author Corvey* @Date 2018年11⽉12⽇15:38:16*/private interface BoundaryDateHandlingStrategy {/** 是否把这个⽇期算作⼀天 */boolean ifCountAsOneDay(Date date);}/*** zkw线段树* @author Corvey*/private static class SegmentTree {private int[] data; // 线段树数据private int numOfLeaf; // 叶⼦结点个数public SegmentTree(int[] srcData) {for (numOfLeaf = 1; numOfLeaf < srcData.length; numOfLeaf <<= 1);data = new int[numOfLeaf << 1];for (int i = 0; i < srcData.length; ++i) {data[i + numOfLeaf] = srcData[i];}for (int i = numOfLeaf - 1; i > 0; --i) {data[i] = data[i << 1] + data[i << 1 | 1];}}/** [left, right]区间求和,left从0开始 */public int query(int left, int right) {if (left > right) {return query(right, left);}left = left + numOfLeaf - 1;right = right + numOfLeaf + 1;int sum = 0;for (; (left ^ right ^ 1) != 0; left >>= 1, right >>= 1) {if ((~left & 1) == 1) sum += data[left ^ 1];if ((right & 1) == 1) sum += data[right ^ 1];}return sum;}}public static void main(String[] args) throws ParseException {System.out.println("测试开始:-------------------");DateFormat df = new SimpleDateFormat("yyyy-MM-dd-HH");Scanner cin = new Scanner(System.in);while (cin.hasNext()) {String l = cin.next();Date start = df.parse(l);String r = cin.next();Date end = df.parse(r);System.out.println(String.format("%s 到 %s, 有%d个⼯作⽇!", df.format(start), df.format(end), howManyWorkday(start, end))); }cin.close();}}。
『zkw线段树及其简单运用』

『zkw 线段树及其简单运⽤』<更新提⽰><第⼀次更新>阅读本⽂前,请确保已经阅读并理解了如下两篇⽂章:<正⽂>引⼊这是⼀种由THU −zkw ⼤佬发明的数据结构,本质上是经典的线段树区间划分思想,采⽤了⾃底向上的⽅式传递区间信息,避免的递归结构,其代码相对经典线段树更简单,常数更⼩,易于实现。
统计的⼒量-源⾃。
基础⾮递归接下来,我们将讲解zkw 线段树的第⼀种实现形式,⽤于单点修改 区间查询,我们以查询区间最⼤值为例来讲解。
建树普通线段树需要建树,zkw 线段树当然也需要建树。
考虑线段树的⼀个性质,其树上的叶节点代表的往往都是形如[x ,x ]的元区间,⽽且除最后⼀层外,线段树是⼀颗满⼆叉树,所以我们要把这颗线段树的数组⼤⼩先申请好了。
⼀棵满⼆叉树有x 个节点时,它有x +12个叶⼦节点,⽽我们需要⾄少n 个叶⼦节点的线段树,即使x +12≥n ,那么我们设x =1,在x +12<n 时不断执⾏x ∗=2,就能得到⾜够⼤⼩的线段树下标base ,由于线段树的叶⼦节点可能分布在两层,所以保险起见,我们还需再将x 扩⼤⼀倍,即在x +1<n 时不断执⾏x ∗=2就可以了。
得到合适的下标位置后,将1−n 下标位置的原数据直接存⼊线段树的叶⼦节点即可。
其实,我们还需将下标再扩⼤两个位置,即需要保证x >n ,才停⽌执⾏x ∗=2。
其原因是这样的:在执⾏区间查询操作时,我们需要将查询区间[l ,r ]更改为(l ,r )(关于原因,我们之后再分析),才便于zkw 线段树的查询,那么在询问[1,n ]时,可能为调⽤到[0,n +1]的原下标,所以还需再扩⼤两个位置。
得到了合适的下标base 并将1−n 的数据存⼊对应位置后,当然我们还要对1到base −1的线段树位置进⾏区间更新,这个普通的更新就可以了。
Code :单点修改直接在叶节点上修改对应的值,然后更新其每⼀个⽗节点即可。
统计的力量-线段树

清华大学 张昆玮
6
2019年10月17日
*计算几何在长期的发展中,
诞生了许多实用的数据结构。
*区间查询,穿刺查询都是计算几何解决的问题 *作为特例中的特例,线段树解决的问题是:
* 一维空间上的几何统计
*高维推广(kd-tree & …)可以进行正交查询
*由于竞赛中涉及计算几何的内容有限,不展开
*计算几何!
2019年10月17日
*for(T[n+=M]=V, n>>=1;n;n>>=1)
* T[n]=T[n+n]+T[n+n+1];
*没了? *没了。
清华大学 张昆玮
*C语言更简单
35
2019年10月17日
*仅使用了两倍原数组的空间 *其中还完整地包括了原数组 *构造容易:
* For i=M-1 downto 1 do T[i]=T[2i]+T[2i+1];
44
2019年10月17日
*标记的传递与收集
懒惰即美德。
清华大学 张昆玮
45
2019年10月17日
清华大学 张昆玮
*区间修改
噩梦的开始
46
2019年10月17日
*RMQ (Range Minimum Query) *区间最小值查询 *线段树 *支持区间修改:
* 某一区间的数值全部增加一个可正可负的数
2019年10月17日
*这不就和树状数组一样了? *本来就应该一样。
*回过头说,树状数组究竟是什么? *就是省掉一半空间后的线段树加上中序遍历 *计算位置需要lowbit什么的 *我们用的是先序遍历,符合人的思考习惯。
线段数讲义

线段树入门(一)路桥中学陈朝晖今天,我们来介绍一种非常特殊的数据结构——线段树。
首先,来看这个问题:给你n个数,仅有两种操作:(1)给第i个数的值添加x(2)询问区间[a,b]的总和是多少CODEVS 1080 线段树练习时间限制: 1s 空间限制: 128000 KB 题目等级 : 钻石 Diamond题目描述 Description一行N个方格,开始每个格子里都有一个整数。
现在动态地提出一些问题和修改:提问的形式是求某一个特定的子区间[a,b]中所有元素的和;修改的规则是指定某一个格子x,加上或者减去一个特定的值A。
现在要求你能对每个提问作出正确的回答。
1≤N<100000,,提问和修改的总数m<10000条。
输入描述 Input Description输入文件第一行为一个整数N,接下来是1行共n个整数,表示格子中原来的整数。
下面是一个正整数m,接下来有m行,表示m个询问,第一个整数表示询问代号,询问代号1表示增加,后面的两个数x和A表示给位置X上的数值增加A,询问代号2表示区间求和,后面两个整数表示a和b,表示要求[a,b]之间的区间和。
输出描述 Output Description共m行,每个整数样例输入 Sample Input645621341 3 52 1 41 1 92 2 6样例输出 Sample Output2222数据范围及提示 Data Size & Hint1≤N≤100000, m≤10000 。
从题目中所给的数据来看,数据量非常大,所需要的查询次数同样非常多。
#include <iostream>usingnamespace std;int dat[Maxn],N,M;struct Tree{int left,right;longlong sum;}tr[Maxn<<2];//tr[i]表示第i线段树,其中[left,right]表示数据区域的边界,sum表示线段树中当前这一段的总和//数组中的每一个结点都将最终设置在线段树的叶子节点位置上,//而线段树中还存在内部结点的存储。
CA一句话题解

1563:四边形不等式推出决策单调,二分决策点.
1568:李超线段树.
1588:权值平衡树.
1798:线段树双标记.但是我是拿LCT写的.
1800:暴力
1821:二分答案+并查集
1853:爆搜
1857:三分套三分.
1875:矩乘优化DP
达式..
2213:DP乱搞一下.附核心代码
for (int i=2;i<=n;++i) ch[i]=getchar(),maxn=max(maxn,ch[i]-'a');
for (int i=1;i<=n;++i)
{
2222:似乎数据有问题还是什么?面向数据了一波.
2223:主席树
2229:GHTree
2241:暴力枚举答案然后检测,检测时候需要特技,感觉理论复杂度并不对但是过了
2243:链剖或者LCT,我写的链剖
2244:cdq分治做三维偏序的DP,答案实际上可能非常大会爆…但是出题人十分懒惰没管那样的数据,所以double就过了
2151:堆+贪心
2154:莫比乌斯反演,有很多种公式化法,我的好像是比较傻逼的那种..
2157:LCT,随便打标记
2163:直接最小割过了,其实应该转对偶图然后最短路才对.
2186:考察线性的逆元求法.
2190:反演分析一下,最后却发现答案是个跟phi有关的表
1007:半平面交
1008:组合数学,需要高精
1010:斜率优化/四边形不等式推决策单调性
可持久化线段树总结

可持久化线段树的总结-----by石门中学柯新宇可持久化线段树,说白了,就是具有一个个时间的树链。
因为修改单点,只会对树上的一条链发生改变,我们只需记录更改过的树链即可。
我们可以发现,每次修改单点时,只会影响到一条链,只需把链存起来就行了。
我才用的方法是:对于每一次修改,开一个root[],记录树根。
(用指针类型,写起来比较方便)struct node{long long s,d; node *left,*right;}tree[(maxn<<5)+(maxn<<3)],*root[maxn];node *newnode(){t0++; tree[t0].s=tree[t0].d=0;tree[t0].left=tree[t0].right=tree;return tree+t0;}root[0]=newnode();for(int i=1;i<=ti;i++) root[i]=newnode();for(int i=1;i<=ti;i++){*root[i]=*root[i-1];updata(root[i],1,lenN,……);……}每一次updata()时要新开节点来储存信息,现将原信息复制给新节点,再做修改。
过程如下:void updata(node *ro,int L,int R,int x,long long v){if(L==R){ro->d++;ro->s+=v;return;}int mid=(L+R)>>1;node *yy=newnode(); //新开节点if(x<=mid){*yy=*ro->left; //拷贝原来的信息ro->left=yy; //将新的节点连给父亲结点updata(ro->left,L,mid,x,v);} else{*yy=*ro->right;ro->right=yy;updata(ro->right,mid+1,R,x,v);}ro->s=ro->left->s+ro->right->s; //up操作ro->d=ro->left->d+ro->right->d;}至于,区间修改,开个懒惰标记记一记就好了,不用每次更新都下发标记(这样会爆空间的),查询的时候加上祖先的标记就好了。
HH神总结的线段树专辑_超经典的
46
scanf("%d",&x[i]);
47
sum += query(x[i] , n - 1 , 0 , n - 1 , 1);
48
update(x[i] , 0 , n - 1 , 1);
49
}
50
int ret = sum;
51
for (int i = 0 ; i < n ; i ++) {
52
53 1));
54
else if (op[0] == 'S') update(a , -b , 1 , n , 1);
55
else update(a , b , 1 , n , 1);
56
}
57
}
58
return 0;
}
o hdu1754 I Hate It 题意:O(-1) 思路:O(-1) 线段树功能:update:单点替换 query:区间最值 ?View Code CPP
37
if (R > m) ret += query(L , R , rson);
38
return ret;
39 }
40 int main() {
41
int T , n;
42
scanf("%d",&T);
43
for (int cas = 1 ; cas <= T ; cas ++) {
44
printf("Case %d:\n",cas);
在代码前先介绍一些我的线段树风格:
maxn 是题目给的最大区间,而节点数要开 4 倍,确切的来说节点数要开大于 maxn 的最小 2x 的两倍
动态序列与动态树问题——浅谈几种常用数据.
动态序列与动态树问题——浅谈几种常用数据结构天津南开中学莫凡*2014年6月5日要动态序列问题是指给定一个序列,在其上执行一系列在线操作(例如一段数的修改、某一区间的反转或某一区间内的最大值查询等等),是算法竞赛中的常见问题,在生产生活中也具有较强的实用价值。
本文重点通过介绍通过几种常用的平衡二叉树型数据结构解决动态序列问题。
动态树问题是动态序列问题的延伸,支持一棵树(或是一片森林)的点上、边上信息的维护以及形态的变换。
由于动态树问题将维护的对象由序列拓展成一棵树,故其在图论中具有重要的实用价值。
由于时间仓促,加上作者水平过弱,不足之处请多多指正。
本文涉及的数据结构虽然都非常简单,但是并不适合初学算法与数据结构的读者,阅读这篇文章的前提条件只有一个:所有涉及的问题你都可以用暴力实现*作者邮箱:w007878@1IndexI几种常用的数据结构41线段树41.1线段树概况 (4)1.2线段树的修改与信息合并 (5)1.3线段树的具体实现与编程细节 (6)1.4另一种实现方式 (6)1.5适用范围 (8)2伸展树92.1平衡树的旋转机制 (9)2.2伸展——Splay的基本操作 (9)2.3懒标记、区间查询和修改 (10)2.4代码实现(C++) (11)2.5优势与弊端 (12)2.6复杂度分析 (13)3Treap153.1Treap=Tree+Heap (15)3.2基于旋转的的Treap (15)3.3重量平衡树 (15)3.4笛卡尔树 (16)3.5核心代码 (17)4实用技巧174.1静态内存回收 (18)4.2数据结构的嵌套 (18)4.3数据结构的可持久化 (19)II动态序列问题2025第k大数205.1不带修改的 (20)5.2带修改的 (21)5.3带插入的 (21)6一些练习题226.1第k大系列 (22)6.2NOI2007项链工厂 (23)6.3NOI2005维护数列 (23)6.4NOI2003文本编辑器 (24)6.5TJOI2014电源插排(w007878强化版) (24)III动态树267DFS序与树链剖分267.1在海棠花开的年代 (26)7.2Begoina的实现 (27)7.3用剖分找LCA (28)7.4复合问题 (29)8link-cut-tree298.1动态维护的剖分 (29)8.2具体的做法 (30)8.3代码很简单 (30)9子树问题与Toptree319.1基于节点收缩的动态树 (31)9.2仍然是剖分的思想 (31)10练习题们3210.1SDOI2011染色(BZOJ2243) (32)10.2tree(伍一鸣) (32)10.3WC2006水管局长数据加强版(BZOJ2594) (32)IV鸣谢333Part I几种常用的数据结构这一部分着重介绍线段树、Splay、Treap这三种数据结构的实现、复杂度分析以及各自适用的范围。
线段树应用PPT课件
线段树与数组的比较
总结词
线段树在处理复杂查询时优 于数组
详细描述
数组虽然便于随机访问,但 在处理复杂查询如区间最大 值、最小值等问题时,线段 树能够提供更快的查询速度
。
总结词
线段树在处理动态数据集时更具优势
详细描述
当数据集频繁变动时,线段树能够快速地 更新和维护数据,而数组可能需要重新构 建或采取其他复杂的操作。
空间效率
线段树通过节点间的关系,将大 问题分解为小问题,所需空间相 对较小。
线段树的缺点
01
02
03
节点分裂与合并
在线段树进行插入、删除 等操作时,可能会导致节 点分裂或合并,使得树的 平衡性难以维护。
构造与重建
线段树在处理大规模数据 时,可能需要多次重建, 导致时间复杂度较高。
适用场景限制
线段树适用于区间查询问 题,对于其他类型的问题 可能需要其他数据结构或 算法。
线段树应用ppt课件
目录
• 引言 • 线段树的原理 • 线段树的应用实例 • 线段树与其他数据结构的比较 • 线段树的优缺点分析 • 总结与展望
01 引言
什么是线段树
定义
线段树是一种用于处理区间查询问题 的数据结构,它可以在线段上高效地 执行查询、更新和删除操作。
结构
线段树通常由一个根节点和若干个子 节点组成,每个节点包含一个区间的 信息,并且每个节点与其子节点之间 存在一一对应关系。
总结词
数组在随机访问和存储空间方面更具优势
详细描述
数组能够提供快速的随机访问,并且在相 同的数据量下,数组所需的存储空间可能 比线段树更少。
05 线段树的优缺点分析
线段树的优点
高效区间查询
算法合集之《二分法统计问题》
建立点线段树,范围是[1,1000000],即 每种面额的帐单设为一个叶结点。 如果C[LSON[v]]>0,那么树v中的最小值 一定在它的左子树上。 如果C[RSON[v]]>0,它的最大值在右子 树上;
一种静态统计方法
[例二]IOI2001 MOBILES : 在一个N*N的方格中,开始每个格子里的 数都是0。现在动态地提出一些问题和修改: 提问的形式是求某一个特定的子矩阵(x1,y1)(x2,y2)中所有元素的和;修改的规则是指定某 一个格子(x,y),在(x,y)中的格子元素上加上或 者减去一个特定的值A。现在要求你能对每个 提问作出正确的回答。1≤N≤1024,提问和修改 的总数可能达到60000条。
procedure UPDATA(x,A) begin p←x while (p<=n) do begin C[p]←C[p]+A p←p+LOWBIT(p) end end
维护的费用:logn
求a[1]-a[x]的和 function SUM(x) begin C[p]=a[p- 2k +1]+^+a[p] ans ← 0 p←x 下一个p=x- 2k while (p>0) do begin ans←ans+C[p] p←p-LOWBIT(p) end return ans end
procedure INSERT(x) begin
l←1,r←n
while (l<=r) do begin m=(l+r) div 2 if x<=V[m] LESS[m]←LESS[m]+1 if x =V[m] break if x<V[m] then r ←m –1 else l←m+1 end end
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
*
清华大学 张昆玮
21
2018年7月5日
1 2 4 5 6 3 7
*
清华大学 张昆玮
22
2018年7月5日
* N 的左儿子是 2N * N 的右儿子是 2N + 1 * N 的父亲是 N >> 1 * …… * 不就是个堆存储么?不用讲了吧?
*
清华大学 张昆玮
23
2018年7月5日
1 10 100 101 110 11 111
A
B
C
*
清华大学 张昆玮
12
2018年7月5日
*
功利点说,没啥用的东西咱不学……
清华大学 张昆玮
13
2018年7月5日
*
区区区间和,用的着线段树?
* 直接把原数组处理成前缀和 * For i=2 to n do
* A[i] += A[i-1]
* Ans(a,b) = A[a] - A[b-1]
清华大学 张昆玮
2018年7月5日
树状 数组
线段 树
*
清华大学 张昆玮
43
2018年7月5日
* 我之前用这种写法做过不少题…… * 大家都说我的代码看不懂 * 我说这就是一个树状数组 * 写树状数组的人说没有lowbit * 我说那就算是线段树吧 * 大家不相信非递归的线段树这么短…… * 我:……
*
清华大学 张昆玮
* If ((s and 1) == 0) Answer += Tree[s + 1]; * If ((t and 1) == 1) Answer += Tree[t – 1]; * s = s >> 1, t = t >> 1;
*
清华大学 张昆玮
30
2018年7月5日
* for (s=s+M-1,t=t+M+1;s^t^1;s>>=1,t>>=1) {
*
清华大学 张昆玮
38
2018年7月5日
(…,5)?
1
2 4 8 9 10 5 11 12 6 13 14 3 7 15
*
清华大学 张昆玮
39
2018年7月5日
(…,5)?
1
2 4 8 9 10 5 11 12 6 13 14 3 7 15
*
清华大学 张昆玮
40
2018年7月5日
1-No
2-1 3-No
* Func Change(n,NewValue)
* n += M; * Tree[n] = NewValue; * While n > 1 do
* n = n >> 1; * Tree[n] = Tree[2n] + Tree[2n+1];
*
清华大学 张昆玮
34
2018年7月5日
* for(T[n+=M]=V, n>>=1;n;n>>=1)
*
清华大学 张昆玮
28
2018年7月5日
(0,5)?
1
2 4 8 9 10 5 11 12 6 13 14 3 7 15
*
清华大学 张昆玮
29
2018年7月5日
* Func Query(s,t)
// 询问从s到t闭区间
* s = s – 1, t = t + 1; // 变为开区间 * s += M, t += M; // 找到叶子位置 * While not ((s xor t) == 1) do
14
2018年7月5日
* 原因是区间和的性质非常好 * 满足区间减法 * 区间减法什么的最讨厌了!后面再说! * 不过直接前缀和不是万能的! * 如果修改元素的话……
*
清华大学 张昆玮
15
2018年7月5日
数据结构 直接存储原数组 直接存储前缀和 线段树
修改元素 O(1) O(n) O(logn)
*
清华大学 张昆玮
* 根据 D. E. Knuth 的分类方法
计算机算法可以分为两类:
* 数值算法与非数值算法 * 其中的非数值算法包括:
* 索引 * 分类 * 统计 * ……
*
清华大学 张昆玮
2
2018年7月5日
* 大家都说: * …… * 常数很大? * 不好写? * 难调试? * 想不到? * ……
*
清华大学 张昆玮
24
2018年7月5日
* 字母树! * 存放的是100,101,110,111四个串! * 每个节点存的是以这个节点为前缀的所有节点和 * 例:1中存的是所有四个以1开头的和。 * 似乎从 100 到 111 就正好是原数组 * 貌似……下标减 4 就行了?
*
清华大学 张昆玮
25
树
*
8
2018年7月5日
清华大学 张昆玮
*
如果我给MM的第一印象不到80分的话……
是不是老老实实地早点罢手比较好?
清华大学 张昆玮
9
2018年7月5日
[0,4) [0,2) 0 1 2 [2,4)
[1,4) ?
3
*
清华大学 张昆玮
10
2018年7月5日
* [1,4) in [0,4)
* 左边,[1,2) in [0,2)
*
清华大学 张昆玮
36
2018年7月5日
* 因为树状数组依赖于区间减法 * 区间减法什么的,最讨厌了……后面再讲,再讲 * 反正如果只问问前缀和,不问区间和的话 * 那我也可以只用一倍空间!
*
清华大学 张昆玮
37
2018年7月5日
(…,5)?
1
2 4 8 9 10 5 11 12 6 13 14 3 7 15
*
清华大学 张昆玮
53
2018年7月5日
* 每个节点不存区间最大值T[n]了
存放M[n]=T[n]-T[n>>1]
* 让每一个节点的值都减除它父亲的值 * 区间修改就直接改M[n]。 * 查询就是从要查的节点开始一直加到根。
T[n]=M[n]+M[n>>1]+…+M[1];
清华大学 张昆玮
47
2018年7月5日
* 在线段树上的每个节点增加一个标记 * 表示这一区间被增加过多少 * 在自顶而下的递归过程中 * 如果看到标记,就更新当前节点的值 * 并将标记下传 * 嗯?又要自顶向下?
*
清华大学 张昆玮
48
2018年7月5日
* 其实堆式存储也可以自顶向下访问 * 就是上下各走一次而已 * 但是我们有更好的办法(使劲想想就知道了) * 不再下传标记,而是随用随查 * 在自底向上的查询过程中 * 每向上走一层,就按照对应的标记调整答案 * 仔细想想很有道理。我们愿意把尽可能多的信息
*
清华大学 张昆玮
3
2018年7月5日
* POJ上的某题,时限很紧…… * 大家都用树状数组,但是有人只会用线段树呢? * 而且我可以轻易改出一道不能用树状数组的题 * 在线段树一次次TLE后,有一个ID发帖抱怨 * “下次写一个汇编版非递归线段树,再超时?” * 可是大家都知道,超时的代码已经2k了。 * 其实我写的就是线段树。很快,而且不到1k。
2018年7月5日
* 我们可以直接找到一个数对应的叶子 * 不用自顶向下慢慢地找啊找 * “直接加 4 ”多简单! * …… * 直接找到叶子? * 无限遐想中……
*
清华大学 张昆玮
26
2018年7月5日
*
可以直接找到叶子?
清华大学 张昆玮
27
2018年7月5日
(0,5)?
1
2 4 8 9 10 5 11 12 6 13 14 3 7 15
4-2
5-No
6-3
7-No
*
清华大学 张昆玮
41
2018年7月5日
* 这不就和树状数组一样了? * 本来就应该一样。 * 回过头说,树状数组究竟是什么? * 就是省掉一半空间后的线段树加上中序遍历 * 计算位置需要lowbit什么的 * 我们用的是先序遍历,符合人的思考习惯。
*
清华大学 张昆玮
42
44
2018年7月5日
*
懒惰即美德。
清华大学 张昆玮
45
2018年张昆玮
46
2018年7月5日
* RMQ (Range Minimum Query) * 区间最小值查询 * 线段树 * 支持区间修改:
* 某一区间的数值全部增加一个可正可负的数
* 暴力修改不灵了!
*
存放在树的根部,所以下传标记做什么?
*
清华大学 张昆玮
49
2018年7月5日
*
庄周梦蝶而已
清华大学 张昆玮
50
2018年7月5日
* 一根线段,支持区间染色。
询问区间是否同色?
* 区间染色需要使用染色标记 * 询问的时候就直接看标记嘛
1表示红色,2表示蓝色,3绿色,0杂色
*
清华大学 张昆玮
51
2018年7月5日
* T[n]=T[n+n]+T[n+n+1];
* 没了? * 没了。
*
清华大学 张昆玮
35
2018年7月5日
* 仅使用了两倍原数组的空间 * 其中还完整地包括了原数组 * 构造容易:
* For i=M-1 downto 1 do T[i]=T[2i]+T[2i+1];
* 太好写了!好理解! * 自底向上,只访问一次,而且不一定访问到顶层 * 实践中非常快,与树状数组接近 * 为什么呢?后面再讲。