按照http://www.notonlysuccess.com/牛的博客写了几道题,刷了一周效果不错
1.复习巩固了以前的知识
2.还有改正了一些写线段树毛病
3.改正了在弱数据的影响下的错误方法AC的题目
4.还学到了线段树新的类型题
总体效果不错,总结一下
HDU 1166 赤裸裸的线段树,什么都不用考虑,新手练习用
HDU 1754 依然赤裸裸……一样是单点的操作,求和改为最值,新手练习专用
HDU 1698 基础的区间操作,我每个节点开了以delta域一个sum域,然后1次查询,注意要有延迟操作
HDU 1394 由于序列的特殊性只有0 - n - 1 切每个出现1次,所以每次把头的数移到尾部 都有 ans = ans - a[i] + (n - a[i] - 1);
树状数组比线段树 更容易
POJ 2777 很早之前做的了,忘记了,我最初的线段数之一
POJ 3468 注意用long long 区间加减一个操作把 HDU2698 =换成 +=就OK了
PKU 2528 离散化+线段数 貌似数据比较弱,我的错误的线段树也过了
正解 改为区间的模型 右端点+1,表示端点
HDU 3016 开始读错题了……认为就是一个线段数覆盖的模型呢……然后看错了数据范围,开始按照PKU 2528的正解离散化……后来发现不用离散化果断过了,我的第一到线段树DP,就这么悲剧了
由于只能从左或者右端点下来,所以每个点可以到2个板。然后用线段树建dag图就是一个水的拓扑序DP了
poj 1436 把由于只有整数的区间,所以吧每个单位区间也当作一个点对于a, b的线段 插入 a * 2 到 b * 2 ,解决了相交于端点的问题,很巧妙的转换!!!向大牛学习了~
HDU 2795 一个水题,插入到叶子节点的简单模型,但是开始被我当作一个覆盖的统计模型了,SB了……
PKU 3667 (新学的内容)同时1A掉HOJ2687 还有 HDU 2871也是一个模型的题,让我抑郁的是那个get的实现我本来是打算在线段树中再加3个域,结果这么写太恶心了,然后看一下别人的程序,竟然用一个vector就可以过,感觉数据是不够猥琐呀,其中用了vector的insert和erase 我一直认为这两个函数是巨慢无比的,但是这题的效果还是非常好的 而且发现自己还有不好的习惯,一旦遇到做不动的题就不想写了……
以PKU 3667为例说一下这类题
一个经典的线段树,以前也见过这里类型,思考过但是一直没有做,这次找了一个这样的题A掉了
不过开始脑残了……贡献了几个WA
区间覆盖操作,找满足一定连续长度的最左边的区间
区间属性
int lcnt[N], rcnt[N], maxn[N],chk[N];
lcnt[N] 表示该区间总左边去最多能去多少,rcnt[N]同理是从右边。
chk[N]表示是否被覆盖过。-1表示不合法状态要查询子区间。
manx[N]表示该区间最大的连续长度是多少。
int all(int idx)//函数计算区间的最大长度
pass(int idx)//函数在insert或者query子区间是,继承区间的属性,更改区间的3个值
change(int idx,int d) 当区间的chk值发生改变时,计算重新计算3个值
显然当d==0时//表示该区间没有人住。3个值都为all(idx)
d== 1时为0
update(int idx)函数是最重要的操作,是每次insert回溯时改变父亲区间的属性。是这个线段树的核心
lcnt[idx] = lcnt[LEFT(idx)] + (lcnt[LEFT(idx)] == all(LEFT(idx)) ? lcnt[RIGHT(idx)] : 0);
rcnt[idx] = rcnt[RIGHT(idx)] + (rcnt[RIGHT(idx)] == all(RIGHT(idx)) ? rcnt[LEFT(idx)] : 0);
这个两句比较直观好理解。
maxn[idx] = max(max(maxn[LEFT(idx)], maxn[RIGHT(idx)]), rcnt[LEFT(idx)] + lcnt[RIGHT(idx)]);的计算是直观重要的
不用再算 maxn[idx] = (max(lcnt[idx], rcnt[idx]),maxn[idx])。原因如下
假如左儿子区间不是all的时候lcnt[idx] <= maxn[LEFT(idx)];右儿子区间同理。
而满的时候有lcnt[idx] == rcnt[LEFT(idx)] + lcnt[RIGHT(idx)];右儿子区间同理。
这个性质大大的化简的线段树的讨论过程。使得query时更加方便。
开始的时候由于maxn[idx]算错了,所以一直WA。
max(maxn[LEFT(idx)], maxn[RIGHT(idx)])这两个maxn被我写成了lcnt和rcnt
完全的代码如下,效率有点低,代码还是很直接美观的。
POJ 2892 最直观的是平衡树,用树状数组的findK的功能更酷一些,线段树找第K小也可以,据说有把线段树建和平衡树的一样的方法做?有待研究
此外HOJ 2910 一个线段树DP卡了……而且我无耻的利用了admin的身份看了一下数据,shit……就错了一组数据差1
3月19日更新:
线段树+扫描线的好题 HOJ 2723 POJ 也有原题stars in your window
以每个点位中心建立一个w-eps ,h - eps的矩形,然后求最大的面积带权并
可以证明在这样一个矩形中所有的点,是能包含该点覆盖点最多的矩形,是一个很重要的思想。
然后就是区间最值的问题了,开始的时候还写错了,丢人呀……
HOJ 1400
赤裸裸的线段树DP,但是由于我的英语一直没懂
简单的说一定要从1个开始连续的覆盖才可以。
还有就是插入的时候插入点就可以了,不许要插入区间。
我们可以考察两个线段,如果没有交集的话,那么前面的一条的任何一个子区间都不会被利用到。
如果有交集的话,他被利用的子区间必定包含右端点区间。
顺便HOJ 2920
树状数组,线段树同样可以解,不过这个树状数组求第K大真的很酷,所以又一次上树状数组了
hdu1255 覆盖的面积
我的极其糟糕的写法基本上退化成数了……正解是扫描线+记录被覆盖两次的线段长。查询是O(1)的,而我的方法可能退化成O(nlogn)
代码就不现丑了
POJ 2828 比较直观的算法是二分+树状数组,但是这题确实线段树代码更帅一下!!
基于这样的考虑,最后一个人到的位置一定是队列的确定位置,那么我们把这个人的位置拿掉以后,剩下的队列就是前n - 1个人的位置,那么倒数第二个人同理,取得其在当前队列中的位置。所以只要从后到前确认每个人的位置就可以了!所以此题只要一个query就可以了
query代码如下:
int query(int p, int x)
{
cnt[x]--;
if(left[x] == right[x])
return left[x];
if(cnt[LEFT(x)] >= p)
return query(p, LEFT(x));
else
return query(p - cnt[LEFT(x)], RIGHT(x));
}
pku2886 Who Gets the Most Candies?(AC)
残留题目
六道做不下去了,以后再说吧。
自己的效率实在是太低了……线段树,放一下吧,换AC自动机