动态开点线段树
众所周知,线段树数组的大小是
动态开点线段树没有建树函数,需要用到哪个节点就开哪个,并存储其维护的范围(k << 1,k << 1 | 1了
这样的一棵线段树可以维护负数,但对应的为了处理负数,
其他的就和普通线段树没什么区别了,但因为它作用于维护范围大的时候,所以一般和权值线段树结合
这里有个问题,所以此时线段树数组要开多大?建议开最大值,因为这是个玄学问题,有的时候
关于正确性:可以理解为原先这棵树是虚的,用到哪个节点就把哪个变成现实的,就好像在树上加边加点,对于线段树就体现为左右端点
现在我们来说一下函数(因为通常维护的值较大所以开了全局long long),以单点修改+区间查询为例,这时不需要用lazytag
单点修改
除了和一个点维护的左右端点
因为这时
函数参数:&
void mdf(long long &k,long long x,long long v,long long l,long long r)
{
if(k == 0)
{
k = ++cnt;//这里k的值可改变,所以相当于用到了当前节点就要建上
}
if(l == r)
{
s[k].sum += v;
return;
}
long long mid = (l + r - 1) / 2;//细节,为了处理负数,线段树里所有mid都改成这样
if(x <= mid)
{
mdf(ls(k),x,v,l,mid);//ls(k)=s[k].l,为了方便
}
else
{
mdf(rs(k),x,v,mid + 1,r);//rs(k)=s[k].r
}
s[k].sum = s[ls(k)].sum + s[rs(k)].sum;
}
区间查询
同上,只改动了和
注意在查询的时候也要不断建立新节点,因为不一定在修改时已经遍历过
long long query(long long &k,long long x,long long y,long long l,long long r)//写法同上
{
if(k == 0)
{
k = ++cnt;//建立新节点
}
if(x <= l && r <= y)
{
return s[k].sum;
}
long long mid = (l + r - 1) / 2,ans = 0;
if(x <= mid)
{
ans += query(ls(k),x,y,l,mid);
}
if(y > mid)
{
ans += query(rs(k),x,y,mid + 1,r);
}
return ans;
}
pushdown
这里放一下,有区间修改时使用,和普通版的区别是,如果没有左右子节点,那么就要新建一个进行标记下传
void pd(int k,int l,int r)
{
if(s[k].lz != -1)//有的是区间覆盖为0,所以初值设为-1判断
{
int mid = (l + r - 1) / 2;
if(ls(k) == 0)//标记下传也要更新节点,这是左
{
ls(k) = ++cnt;
}
if(rs(k) == 0)//这是右
{
rs(k) = ++cnt;
}
s[ls(k)].lz += s[k].lz;//懒标记下传
s[rs(k)].lz += s[k].lz;
s[ls(k)].sum += s[k].lz * (mid - l + 1);//sum(维护的值)下传
s[rs(k)].sum += s[k].lz * (r - mid);
s[k].lz = -1;
}
}
函数调用
显然上面两个函数都是动态开点权值线段树,所以调用也应该和权值线段树一样,
但是这里要注意
例题
例1:P5459
题目
题目相当于找有哪些区间区间合在
不难想到倒序遍历
根据数据范围发现
这里函数的参数
这个题数组大小就玄学,直接开了个
#include <bits/stdc++.h>
using namespace std;
#define ls(k) s[k].l
#define rs(k) s[k].r
long long n,d,u,a[100005],b[100005],cnt,MX = 1e10;
struct node
{
long long l,r,sum;
}s[99999999];
void mdf(long long &k,long long x,long long v,long long l = -MX,long long r = MX)
{
if(k == 0)
{
k = ++cnt;
}
if(l == r)
{
s[k].sum += v;
return;
}
long long mid = (l + r - 1) / 2;
if(x <= mid)
{
mdf(ls(k),x,v,l,mid);
}
else
{
mdf(rs(k),x,v,mid + 1,r);
}
s[k].sum = s[ls(k)].sum + s[rs(k)].sum;
}
long long query(long long &k,long long x,long long y,long long l = -MX,long long r = MX)
{
if(k == 0)
{
k = ++cnt;
}
if(x <= l && r <= y)
{
return s[k].sum;
}
long long mid = (l + r - 1) / 2,ans = 0;
if(x <= mid)
{
ans += query(ls(k),x,y,l,mid);
}
if(y > mid)
{
ans += query(rs(k),x,y,mid + 1,r);
}
return ans;
}
int main()
{
scanf("%lld%lld%lld",&n,&d,&u);
for(long long i = 1;i <= n;i++)
{
scanf("%lld",&a[i]);
b[i] = b[i - 1] + a[i];
}
long long rt = 0,ans = 0;
for(long long i = n;i >= 1;i--)
{
mdf(rt,b[i],1ll);
ans += query(rt,d + b[i - 1],u + b[i - 1]);
}
printf("%lld",ans);
return 0;
}
例2:CF915E
题目
初始都是工作日,易想到去维护非工作日,答案就是总天数
注意到
但是这个毒瘤题卡常,要写快读,本喵不会所以悲惨TLE #7,想过要去写珂朵莉树qaq
#include <bits/stdc++.h>
using namespace std;
#define ls(k) s[k].l
#define rs(k) s[k].r
int n,q,cnt;
struct tree
{
int l,r,lz = -1,sum;
}s[9999999];
void pd(int k,int l,int r)
{
if(s[k].lz != -1)//这里如果初值为0就错了,无法区间覆盖0
{
int mid = (l + r - 1) / 2;
if(ls(k) == 0)
{
ls(k) = ++cnt;
}
if(rs(k) == 0)
{
rs(k) = ++cnt;
}
s[ls(k)].lz = s[k].lz;
s[rs(k)].lz = s[k].lz;
s[ls(k)].sum = s[k].lz * (mid - l + 1);
s[rs(k)].sum = s[k].lz * (r - mid);
s[k].lz = -1;
}
}
void mdf(int &k,int x,int y,int v,int l,int r)
{
if(k == 0)
{
k = ++cnt;
}
if(x <= l && r <= y)
{
s[k].lz = v;
s[k].sum = v * (r - l + 1);
return;
}
pd(k,l,r);
int mid = (l + r - 1) / 2;
if(x <= mid)
{
mdf(ls(k),x,y,v,l,mid);
}
if(y > mid)
{
mdf(rs(k),x,y,v,mid + 1,r);
}
s[k].sum = s[ls(k)].sum + s[rs(k)].sum;
}
int query(int &k,int x,int y,int l,int r)
{
if(k == 0)
{
k = ++cnt;
}
if(x <= l && r <= y)
{
return s[k].sum;
}
pd(k,l,r);
int mid = (l + r - 1) / 2,ans = 0;
if(x <= mid)
{
ans += query(ls(k),x,y,l,mid);
}
if(y > mid)
{
ans += query(rs(k),x,y,mid + 1,r);
}
return ans;
}
int main()
{
scanf("%d%d",&n,&q);
int rt = 0;
while(q--)
{
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
if(k == 1)
{
mdf(rt,l,r,1ll,1,n);
}
else
{
mdf(rt,l,r,0ll,1,n);
}
printf("%d\n",n - query(rt,1,n,1,n));
}
return 0;
}
例3:CF817F
题面
这题挺难评的,也不算严格的动态开点线段树,只能说是离散化+进阶懒标记,只是开个结构体记录一下区间端点而已
题目相当于两种操作:区间覆盖为 屎山懒标记
开一个结构体表示线段树,要有变量
考虑懒标记怎么设立,这东西是用于将上层节点中的修改操作,等到pushdown时再下传给子节点,并把当前节点的清空,感性理解为延迟更新操作(过年长辈收红包不肯发给小辈直到更大的长辈来检查),所以要保证它的值可以和修改操作一一对应
重点:懒标记代表的修改状态是这个节点刚刚“被修改”过的,还没给子节点下传的,也就是说假设懒标记代表要覆盖为
不妨设
pushdown函数要分类讨论先判断当前节点
- 看来子节点并没有存储懒标记,直接反转该区间和,并将它的
lz 设为3 - 这说明它要把下面的区间都改成
0 ,且自身现在和为0 ,那么把它的区间和改成区间长度(每个元素都是1 ),且lz 改成2 - 和
lz=1 相反,于是把区间和改成0 ,向下的懒标记被不幸改成了覆盖0 的状态1 - 这代表还未下传的反转标记可以撤回啦,不过还要把当前区间和
sum 改成len-sum
BUT,真的需要这么麻烦吗??
结论:不管怎样区间和都要反转,而
因为如果没有懒标记则需要反转,如果已经是反转则抵消了,覆盖成
终于,写完了标记下传,我忒难了QAQ
现在来考虑mdf函数,mdf1是普普通通的区间覆盖,mdf2是奇奇怪怪的区间反转,当前节点对应的区间被修改区间完全包含时直接上前面的结论,改当前节点维护的值
说说query,对于一个区间
这里因为不可能维护
离散化时不但要将数组整体缩到
每次修改操作按照离散化后的区间执行,而查询操作输出当前被缩小的值对应的原数
#include <bits/stdc++.h>
using namespace std;
#define ls(k) s[k].l
#define rs(k) s[k].r
long long n,b[3000005],c[3000005],d[3000005];
struct node
{
long long f,l,r;
}a[3000005];
struct tree
{
long long l,r,lz,sum;
}s[2000005];
void build(long long k,long long l,long long r)
{
ls(k) = l;
rs(k) = r;
if(l == r)
{
return;
}
long long mid = (l + r) / 2;
build(k << 1,l,mid);
build(k << 1 | 1,mid + 1,r);
}
void pd(long long k,long long l,long long r)
{
if(s[k].lz != 0 && ls(k) != rs(k))
{
if(s[k].lz != 3)
{
s[l].lz = s[k].lz;
s[l].sum = (s[k].lz - 1) * (rs(l) - ls(l) + 1);
s[r].lz = s[k].lz;
s[r].sum = (s[k].lz - 1) * (rs(r) - ls(r) + 1);
}
else
{
s[l].lz = 3 - s[l].lz;
s[l].sum = rs(l) - ls(l) + 1 - s[l].sum;
s[r].lz = 3 - s[r].lz;
s[r].sum = rs(r) - ls(r) + 1 - s[r].sum;
}
s[k].lz = 0;
}
}
void mdf1(long long k,long long x,long long y,long long v)
{
if(x <= ls(k) && rs(k) <= y)
{
s[k].lz = v;
s[k].sum = (v - 1) * (rs(k) - ls(k) + 1);
return;
}
pd(k,k << 1,k << 1 | 1);
long long mid = (ls(k) + rs(k)) / 2;
if(x <= mid)
{
mdf1(k << 1,x,y,v);
}
if(mid < y)
{
mdf1(k << 1 | 1,x,y,v);
}
s[k].sum = s[k << 1].sum + s[k << 1 | 1].sum;
}
void mdf2(long long k,long long x,long long y)
{
if(x <= ls(k) && rs(k) <= y)
{
s[k].lz = 3 - s[k].lz;
s[k].sum = rs(k) - ls(k) + 1 - s[k].sum;
return;
}
pd(k,k << 1,k << 1 | 1);
long long mid = (ls(k) + rs(k)) / 2;
if(x <= mid)
{
mdf2(k << 1,x,y);
}
if(mid < y)
{
mdf2(k << 1 | 1,x,y);
}
s[k].sum = s[k << 1].sum + s[k << 1 | 1].sum;
}
long long query(long long k)
{
if(s[k].sum == 0)
{
return ls(k);
}
if(s[k].sum == rs(k) - ls(k) + 1)
{
return 0;
}
pd(k,k << 1,k << 1 | 1);
long long x = query(k << 1);
if(x != 0)
{
return x;
}
x = query(k << 1 | 1);
return x;
}
int main()
{
scanf("%lld",&n);
for(long long i = 1;i <= n;i++)
{
scanf("%lld%lld%lld",&a[i].f,&a[i].l,&a[i].r);
b[(i - 1) * 4 + 1] = a[i].l;
b[(i - 1) * 4 + 2] = a[i].l - 1;
b[(i - 1) * 4 + 3] = a[i].r;
b[i * 4] = a[i].r + 1;
}
b[n * 4 + 1] = 1;//防止出现1 0 0 1 ...
sort(b + 1,b + n * 4 + 2);
long long cnt = 0;
for(long long i = 1;i <= n * 4 + 1;i++)
{
if(b[i] != b[i - 1])
{
c[b[i]] = ++cnt;//离散化后的值
d[cnt] = b[i];//对应的原数
}
}
build(1,1,cnt);
for(long long i = 1;i <= n;i++)
{
if(a[i].f != 3)
{
mdf1(1,c[a[i].l],c[a[i].r],3 - a[i].f);
}
else
{
mdf2(1,c[a[i].l],c[a[i].r]);
}
printf("%lld\n",d[query(1)]);//显然此时query返回的不是答案
}
return 0;
}