动态开点真是个好东西
什么是动态开点
就是一种线段树实现的一种技巧 。
具体来说,就是我们发现在线段树中,有很多对于答案还没有贡献的空节点,如果采用堆式存储法,会浪费很多没有必要的空间来存空节点,而这些节点有可能到最后也不会用到,这就是一种实质上的浪费。
当然,只开几棵线段树其实并不会影响那么多,动态开点也有点繁琐,没有必要为了一点空间去使用动态开点。
但是,如果你开了
作者犯糖了,放了一道重剖的题目
学习单纯的动态开点请以这道题为例
以P3313 [SDOI2014] 旅行为例做一个讲解
P3313 [SDOI2014] 旅行
题目描述
S 国有
为了方便,我们用不同的正整数代表各种宗教,S 国的居民常常旅行。旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。当然旅程的终点也是信仰与他相同的城市。S 国为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。
在 S 国的历史上常会发生以下几种事件:
CC x c:城市x 的居民全体改信了c 教;CW x w:城市x 的评级调整为w ;QS x y:一位旅行者从城市x 出发,到城市y ,并记下了途中留宿过的城市的评级总和;QM x y:一位旅行者从城市x 出发,到城市y ,并记下了途中留宿过的城市的评级最大值。
由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。
输入格式
输入的第一行包含整数
接下来
接下来
接下来
输出格式
对每个 QS 和 QM 事件,输出一行,表示旅行者记下的数字。
输入输出样例 #1
输入 #1
5 6
3 1
2 3
1 2
3 3
5 1
1 2
1 3
3 4
3 5
QS 1 5
CC 3 1
QS 1 5
CW 3 3
QS 1 5
QM 2 4
输出 #1
8
9
11
3
说明/提示
对于
数据保证对所有 QS 和 QM 事件,起点和终点城市的信仰相同;在任意时刻,城市的评级总是不大于
思路
这道题目乍一看十分的逆天,但是仔细分析来看,只要对于每一种宗教都建一棵线段树,维护信仰
好个啥。。。你的空间不炸了?一共可能用
所以我们就需要动态开点,如果每棵线段树只需要
不过还有一个问题,改教(操作CC)要怎么处理?
也很简单,只要在城市
Talk Is CHEAP,Show you My Code
#include<bits/stdc++.h>
#define int long long
#define bug cout<<"BUG\n"
#define V vector
using namespace std;
const int N=2e5+10;
const int INF=1e9+10;
int root[N],idx=0;
struct node{
int ls,rs,w,maxn;
}a[N<<4];
inline int ls(int x){return a[x].ls;}
inline int rs(int x){return a[x].rs;}
void push_up(int x){
a[x].w=a[ls(x)].w+a[rs(x)].w;
a[x].maxn=max(a[ls(x)].maxn,a[rs(x)].maxn);
}
void update(int &x,int l,int r,int pos,int w){
if(!x) x=++idx;//这里和主席树不同
a[x].maxn=max(a[x].maxn,w);a[x].w+=w;
if(l==r)return;
int mid=l+r>>1;
if(pos<=mid) update(a[x].ls,l,mid,pos,w);
else update(a[x].rs,mid+1,r,pos,w);
}
void remove(int &x,int l,int r,int pos){
if(l==r){
a[x].maxn=a[x].w=0;
return;
}
int mid=l+r>>1;
if(pos<=mid) remove(a[x].ls,l,mid,pos);
else remove(a[x].rs,mid+1,r,pos);
push_up(x);
}
int query_sum(int x,int l,int r,int L,int R){
if(l>R||r<L) return 0;
if(l>=L&&r<=R) return a[x].w;
int mid=l+r>>1;
return query_sum(a[x].ls,l,mid,L,R)+query_sum(a[x].rs,mid+1,r,L,R);
}
int query_max(int x,int l,int r,int L,int R){
if(l>R||r<L) return -INF;
if(l>=L&&r<=R) return a[x].maxn;
int mid=l+r>>1;
return max(query_max(a[x].ls,l,mid,L,R),query_max(a[x].rs,mid+1,r,L,R));
}
V<V<int> >e;
V<int>siz,son,fa,dep,id,xy/*信仰*/,top;
int cnt=0;
void dfs1(int u,int fat){
fa[u]=fat;
dep[u]=dep[fat]+1;
siz[u]=1;
for(int v:e[u]){
if(v==fat)continue;
dfs1(v,u);
siz[u]+=siz[v];
if(!son[u]||siz[son[u]]<siz[v]) son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;
id[u]=++cnt;
if(!son[u])return;
dfs2(son[u],tp);
for(int v:e[u]){
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);
}
}
int n;
int query_range_sum(int u,int v,int zj){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans+=query_sum(root[zj],1,n,id[top[u]],id[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans+=query_sum(root[zj],1,n,id[u],id[v]);
return ans;
}
int query_range_max(int u,int v,int zj){
int ans=-INF;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=max(ans,query_max(root[zj],1,n,id[top[u]],id[u]));
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=max(ans,query_max(root[zj],1,n,id[u],id[v]));
return ans;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int q;cin>>n>>q;
e.resize(n+1);fa.resize(n+1);dep.resize(n+1);
siz.resize(n+1);son.resize(n+1);id.resize(n+1);
xy.resize(n+1);top.resize(n+1);
V<int>pj(n+1),zj(n+1);
for(int i=1;i<=n;i++) cin>>pj[i]>>zj[i];
for(int i=1,a,b;i<n;i++){
cin>>a>>b;
e[a].push_back(b);e[b].push_back(a);
}
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=n;i++) update(root[zj[i]],1,n,id[i],pj[i]);
while(q--){
string op;
int x,y;
cin>>op>>x>>y;
if(op=="CC"){
remove(root[zj[x]],1,n,id[x]);
update(root[y],1,n,id[x],pj[x]);
zj[x]=y;
}
if(op=="CW"){
remove(root[zj[x]],1,n,id[x]);
update(root[zj[x]],1,n,id[x],y);
pj[x]=y;
}
if(op=="QS") cout<<query_range_sum(x,y,zj[x])<<"\n";
if(op=="QM") cout<<query_range_max(x,y,zj[x])<<"\n";
}
return 0;
}
本题的评级是
比如这道就是一道
后记
2025.10.16 updated,我实在是太唐了,不小心放了一道重剖,thxMyself
2025.10.17 updated,修改了一处笔误,thx一个喜欢打aqtw的人