写在前面
终于打算好好整理笔记了,整理真的是提升水平的最关键要素之一,至少这个方法真的适合自己,为什么之前一直没有做?一个字,懒,两个字,太懒,三个字,执行力
好了好了
这一篇是线段树区间合并,
核心思想是:是否跨越分治中心,维护前后缀信息
经典场景:连续区间最大长度,线段树解决最大子段和
poj 3667
题意
初始0,两种操作,
查询:len 查找能够放置区间的最左端,并修改成1
修改:pos,len 从pos开始的长度为len的区间修改为0
思路
找最左边的符合的连续区间长度就好了
考虑线段树节点之间合并,即信息维护:
需要4个Info和1个tag标记
pre,suf,sum,len,分别表示区间前缀连续段长度,区间后缀连续段长度,区间内最大连续段长度,区间长度
tag,懒标记,-1表示空节点,0表示标记为空,1表示标记为入住(占用),使用注意:build就要初始化lazy标记,使用需要考虑标记合并于标记应用于节点,不要忘记标记下传
合并节点:分别考虑如何更新各个info
区间合并的题目大部分都是这么合并的,详细见代码(也是简单的一点)
区间修改:lazy_tag的魔力,settag即可,然后记得modify和query都push_down
settag:给节点打标记,修改在节点上执行,本题我将标记合并与应用标记都写在settag中
然后这个题目的最左端点的查询:重点关注查询顺序,先左区间,再看中缀,最后右区间,端点就是区间长度+端点就行了,到了叶子就直接是端点
点击查看代码
/*
线段树合并:
实际上是找一段最大连续区间,并且满足最左边即可
考虑最大连续区间:经典线段树合并可以解决最大连续区间长度, 核心是是否跨过分治中心
考虑最左端点:结论是考虑查找顺序,先考虑左边,再考虑跨过分治中心,最后考虑右边
如果查找的区间长度是包含在其中的就可以放了,然后返回左端点就行
端点查找具体位置:左前缀l,中点mid-lsuf,右后缀:r-rsuf
pre,suf,sum区间最大连续段长度
*/
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN=2e5+10;
struct Info{
int pre,suf,sum,len;
Info(){}
Info(int x,int l):pre(x),suf(x),sum(x),len(l){}
};
Info operator+(const Info &l,const Info &r){
Info res;
res.len = l.len + r.len,res.pre = l.pre,res.suf=r.suf;
if(l.len == l.pre) res.pre = l.pre+r.pre;
if(r.len == r.suf) res.suf = r.suf+l.suf;
res.sum = max(l.sum,r.sum);
res.sum = max(res.sum, l.suf + r.pre);
return res;
}
struct SegTree{
Info s;
}seg[MAXN*4];
struct Tag{
int t;
}tag[MAXN*4];
void settag(int idx,int op){
tag[idx].t=op;
if(op == 0) seg[idx].s.sum = seg[idx].s.pre = seg[idx].s.suf = seg[idx].s.len;
else seg[idx].s.sum = seg[idx].s.suf = seg[idx].s.pre = 0;
}
void push_down(int idx){
if(tag[idx].t!=-1){
settag(idx*2,tag[idx].t);
settag(idx*2+1,tag[idx].t);
tag[idx].t=-1;
}
}
void build(int l,int r,int idx){
tag[idx].t = -1;
if(l == r){seg[idx].s=Info(1,1);return;}
int mid=(r+l)>>1;
build(l,mid,idx*2);
build(mid+1,r,idx*2+1);
seg[idx].s=seg[idx*2].s+seg[idx*2+1].s;
}
void modify(int l,int r,int idx,int ql,int qr,int op){
if(ql<=l && r <= qr){settag(idx,op);return;}
push_down(idx);
int mid = (r+l) >> 1;
if(ql<=mid) modify(l,mid,idx*2,ql,qr,op);
if(qr > mid) modify(mid+1,r,idx*2+1,ql,qr,op);
seg[idx].s=seg[idx*2].s + seg[idx*2+1].s;
}
int query(int l,int r,int idx,int len){
push_down(idx);
if(seg[idx].s.sum < len) return 0;
if(l == r) return l;
int mid = (l+r) >> 1;
if(seg[idx*2].s.sum >= len) return query(l,mid,idx*2,len);
else if(seg[idx*2].s.suf + seg[idx*2+1].s.pre >= len) return mid - seg[idx*2].s.suf + 1;
else return query(mid+1,r,idx*2+1,len);
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int n,q;cin >> n >> q;
build(1,n,1);
while(q--){
int op, len,pos; cin >> op;
if(op == 1) {
cin>>len; pos = query(1,n,1,len);cout<<pos<<'\n';
if(pos>0)modify(1,n,1,pos,pos+len-1,1);
}
else {
cin>>pos>>len; modify(1,n,1,pos,pos+len-1,0);
}
}
return 0;
}
Hdu 1540
这个题目是之前写得,有点忘记了,这里就放之前的记录了,总之又是一个调了一下午的题目,最后实在没辙上了AI,可恶
题意
初始数组全为1,三种操作:
操作1,位置变0
操作2,查询位置所处的最长连续1段
操作3,0恢复1
思路
线段树合并
线段树,从位置中点开始往左右两边查询前后缀就是答案
点击查看代码
/*
线段树合并关键点总结:
1.相邻区间合并到一起
2.考虑跨过分治点,即前缀和后缀,更新就是取max()
想一想线段是怎么合并的?
ans=左区间+右区间,左后缀+右前缀
区间后缀和前缀是怎么计算的?
区间后缀 = 左后缀+右区间len,右后缀()
查询是找左区间后缀+右区间前缀
卡点:
爆内存调了1h看不懂是为什么
build和change没有return
错了是为什么?
1.最近的一次摧毁记录错误 -> 栈/数组记录,我用了个变量记录,大错特错
2.信息更新错误 -> 父亲接受儿子的信息错误,这个题目是连续1的数量,我还是按照子段和的逻辑更新信息,但是这里是有条件的,子段和可以不接都行,但是这里不一样,必须能接上才能更新
3.信息查询错误,完全不懂怎么查询 -> 就跟单点修改一样定位查询就行,怎么精准定位?
有说法的:查看pos是否包含在某一段连续区间内,如果在就直接返回这段区间(合并的左+右)就行了,否则就说明还没在一块连通块内,那就再靠近一点点!
直到碰到了叶子结点
4.忘了建树
*/
#include <cstdio>
#include <stack>
using namespace std;
const int MAXN = 50010;
struct Info {
int mpre, msuf, len;
Info() : mpre(0), msuf(0), len(0) {}
Info(int v, int l) : len(l) {
mpre = msuf = v;
}
};
Info operator+ (const Info& l, const Info& r) {
Info res;
res.len = l.len + r.len;
res.mpre = l.mpre;
if (l.mpre == l.len) {
res.mpre = l.len + r.mpre;
}
res.msuf = r.msuf;
if (r.msuf == r.len) {
res.msuf = r.len + l.msuf;
}
return res;
}
struct SegTree {
Info s;
} seg[MAXN * 4];
void build(int l, int r, int idx) {
if (l == r) {
seg[idx].s = Info(1, 1);
return;
}
int mid = (l + r) >> 1;
build(l, mid, idx * 2);
build(mid + 1, r, idx * 2 + 1);
seg[idx].s = seg[idx * 2].s + seg[idx * 2 + 1].s;
}
void change(int l, int r, int idx, int pos, int val) {
if (l == r) {
seg[idx].s = Info(val, 1);
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) {
change(l, mid, idx * 2, pos, val);
} else {
change(mid + 1, r, idx * 2 + 1, pos, val);
}
seg[idx].s = seg[idx * 2].s + seg[idx * 2 + 1].s;
}
Info query(int l, int r, int idx, int ql, int qr) {
if (ql <= l && r <= qr) {
return seg[idx].s;
}
int mid = (l + r) >> 1;
if (qr <= mid) {
return query(l, mid, idx * 2, ql, qr);
}
if (ql > mid) {
return query(mid + 1, r, idx * 2 + 1, ql, qr);
}
Info left_info = query(l, mid, idx * 2, ql, mid);
Info right_info = query(mid + 1, r, idx * 2 + 1, mid + 1, qr);
return left_info + right_info;
}
int main() {
int n, m;
while (scanf("%d%d", &n, &m) == 2) {
build(1, n, 1);
stack<int> stk;
while (m--) {
char op[2];
scanf("%s", op);
if (op[0] == 'D') {
int pos;
scanf("%d", &pos);
change(1, n, 1, pos, 0);
stk.push(pos);
} else if (op[0] == 'R') {
if (!stk.empty()) {
int pos = stk.top();
stk.pop();
change(1, n, 1, pos, 1);
}
} else if (op[0] == 'Q') {
int pos;
scanf("%d", &pos);
int left_len = query(1, n, 1, 1, pos).msuf;
int right_len = query(1, n, 1, pos, n).mpre;
int ans = left_len + right_len-1;
if (ans < 0) ans = 0;
printf("%d\n", ans);
}
}
}
return 0;
}
然后我就来挑战一下09年多校,被打爆了
Hdu 2871
真的做不动一点。。。到此为止了。。。

浙公网安备 33010602011771号