1.一小段废话
最近事情真的很多,各种期中考试,大作业什么什么的,再加上树链剖分非常好调试码量非常小,所以刷的题很少能刷都不错了好嘛。笔者刚刚赶完明天要交的论文,就来写小结了,以后要不就按周写小结吧,显得有条理一些。刚学了树链剖分,这周大部分的时间做树剖,水了不少模板题。水平有限,总结是写给自己看的,可能写的不太好。
2.树链剖分的基本概念与实现
前置知识:图遍历(dfs),线段树
假如有一个数组,需要维护其一些性质,如:最大值,最小值,异或和,区间和,颜色数等,我们可以考虑用线段树维护。但我们需要维护树上的两个点(u,v)的路径上经过的点的性质,就需要用到今天所述的树链剖分了。
2.1树链剖分基础概念
重儿子:某结点的子结点中大小最大的结点。
轻儿子:某结点除了重儿子其他的结点,特别的是,根结点是轻儿子。
重链:链上所有元素(除了顶点)都是重儿子。
轻链:其他的链。
如果这些概念比较枯燥的话,可以通过一张图来直观感受。(假定一号结点为根节点,结点编号为1~n)
如图所示,加粗的结点就是一条重链
2.2具体算法实现
树链剖分时,实现两次dfs。第一次dfs的目的:处理出结点的深度,重儿子,父亲结点,由于重儿子定义可知,我们需要处理出每个结点的子树的大小。第二次dfs:处理出每条链顶端的元素,按dfs的顺序处理出每个结点的新编号idx,然后进行后续线段树的操作。
2.3对变量的解释
dep[u]:记录u结点的深度。
fa[u]:记录u结点的父亲结点。
sz[u]:记录以u为根的子树大小。
son[u]:记录u的重儿子。
top[u]:记录u所在链的链头元素。
idx[u]:记录u结点的新编号。
nw[cnt]:新编号对应的权值
3.题目
3.1洛谷 P3384模板题
操作:1.将x到y路径的结点全部加上k,我们将top[x]和top[y]深的那一个不断向上跳,通过线段树累加实现区间加。
2.基本与1一致,将区间加改为查找。
3.我们处理出的dfs序一定满足这样的规律:第i个元素是某颗子树的根结点,sz[i]是这颗子树的大小,从i~i+sz[i]-1也全是这颗子树上的元素,所以处理以x为结点的子树就是线段树上处理x~x+sz[x]-1,将该区间每个元素加上z,用线段树维护。
4.操作基本与3一致,将区间加改为查找。
注意事项:1.线段树区间加操作记得维护懒标记。
2.记得开long long。
直接贴代码吧。
#include<iostream>
#include<vector>
#include<cstring>
#define ll long long
#define int long long
using namespace std;
using namespace std;
const int N = 1e5 + 10;
ll w[N], nw[N];
ll sumv[N << 2], tag[N << 2];
int Size[N], son[N], top[N], dep[N], fa[N],n, m, r, p,cnt, id[N];
vector<int>e[N];
void pushup(int id) {
sumv[id] = sumv[id << 1] + sumv[id << 1 | 1];
//sumv[id] %= p;
}
void build(int id, int l, int r) {
if (l == r) {
sumv[id] = nw[l];
return;
}
int mid = l + r >> 1;
build(id << 1, l, mid);
build(id << 1|1, mid + 1, r);
pushup(id);
}
void pushdown(int id, int l, int r) {
if (tag[id] == 0)return;
ll k = tag[id];
tag[id << 1] += k;
tag[id << 1 | 1] += k;
int mid = l + r >> 1;
sumv[id << 1] += (mid - l + 1) * k;
sumv[id << 1 | 1] += (r - mid) * k;
tag[id] = 0;//回收标记不能忘
}
void add(int id, int l, int r, int x, int y, ll k) {
if (x <= l && y >= r) {
sumv[id] += (r - l + 1) * k;
//sumv[id] %= p;
tag[id] += k;
return;
}
pushdown(id, l, r);
int mid = l + r >> 1;
if (x <= mid)add(id << 1, l, mid, x, y, k);
if (y > mid)add(id << 1 | 1, mid + 1, r, x, y, k);
pushup(id);
}
void modify(int u, int v,ll k) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])swap(u, v);
add(1, 1, n, id[top[u]], id[u], k);
u = fa[top[u]];
}
if (dep[u] < dep[v])swap(u, v);
add(1, 1, n, id[v], id[u],k);
return;
}
int ask(int id, int l, int r, int x, int y) {
if (x <= l && y >= r)return sumv[id];
pushdown(id, l, r);
int mid = l + r >> 1;
ll ans = 0;
if (x <= mid)ans += ask(id << 1, l, mid, x, y);
if (y > mid)ans += ask(id << 1 | 1, mid + 1, r, x, y);
//ans %= p;
return ans;
}
int query(int u, int v) {
ll res = 0;
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]])swap(u, v);
res += ask(1, 1, n, id[top[u]], id[u]);//累加权值和
res %= p;
u = fa[top[u]];
}
if (dep[u] < dep[v])swap(u, v);
res += ask(1, 1, n, id[v], id[u]);
return res;
}
void dfs1(int u, int f) {
//处理出深度dep,Size大小,fa父亲结点,重儿子son
dep[u] = dep[f] + 1;
Size[u] = 1;
fa[u] = f;
for (int v : e[u]) {
if (v == f)continue;
dfs1(v, u);
Size[u] += Size[v];
if (Size[son[u]] < Size[v])son[u] = v;
}
}
void dfs2(int u, int t) {
//处理每一条链的顶点top
//结点被剖后的新编号id,新编号对应的新权值nw
top[u] = t;
id[u] = ++cnt;//存储剖分后的新编号
nw[cnt] = w[u];//存储新编号在树中的权值
if (!son[u])return;
dfs2(son[u], t);
for (int v : e[u]) {
if (v == fa[u] || son[u] == v)continue;//不往重儿子和父亲走
dfs2(v, v);
}
}
signed main() {
cin >> n >> m >> r >> p;
for (int i = 1; i <= n; i++)cin >> w[i];
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(r, 0);
dfs2(r, r);
build(1, 1, n);
/*for (int i = 1; i <= n; i++)cout << nw[i] << ' ';
cout << endl;
for (int i = 1; i <= n; i++)cout << ask(1, 1, n, i, i) << ' ';
cout << endl;*/
while (m--) {
int op;
cin >> op;
if (op == 1) {
int x, y;
ll z;
cin >> x >> y >> z;
modify(x, y, z);
}
else if (op == 2) {
int x, y;
cin >> x >> y;
cout << query(x, y)%p << endl;
}
else if (op == 3) {
int root;
ll k;
cin >> root >> k;
add(1, 1, n, id[root], id[root] + Size[root] - 1, k);
}
else if (op == 4) {
int root;
cin >> root;
cout << ask(1, 1, n, id[root], id[root] + Size[root] - 1)%p<<endl;
}
}
return 0;
}
3.2洛谷P2146软件包管理器
写这道题的状态非常好啊,一发就过了。“如果软件包 a 依赖软件包 b,那么安装软件包 a 以前,必须先安装软件包 b。”同时,如果想要卸载软件包 b,则必须卸载软件包 a。”题意分析:我们只有两个操作,install安装和uninstall卸载,安装操作一定是自上而下的,卸载操作一定是自下而上的,不妨假定软件包已安装为1,未安装为0,维护区间和即可。至于安装和卸载操作:对于安装,就是查询从树根到当前结点的0的个数,可以用点深度减去线段树维护的区间和,然后再将这段区间全部修改为1。卸载就是把安装反过来。本题几乎没有坑点,考虑好如何维护标记即可。
部分代码如下。线段树部分略。
void uninstall(int x){//即查询x的子树的1的个数
cout<<ask(1,1,n,id[x],id[x]+sz[x]-1)<<endl;
modify(1,1,n,id[x],id[x]+sz[x]-1,0);//区间内修改为0
}
void install(int u){
//即查询从根节点1~x的这条链的0的个数
//而线段树维护的是1的个数,我们只需要用总数(可以用dep[u]代替)-sumv即可
int ans=0;
int sum=dep[u];
while(top[u]!=top[1]){
//if(dep[top[x]]<dep[top[1]])swap(x,1);//感觉这句话没有必要
ans+=ask(1,1,n,id[top[u]],id[u]);
modify(1,1,n,id[top[u]],id[u],1);//全部改成1
u=fa[top[u]];
}
//if(dep[u]>dep[1])swap(x,1);//同上 感觉是废话
ans+=ask(1,1,n,id[1],id[u]);
modify(1,1,n,id[1],id[u],1);//全部下载
cout<<sum-ans<<endl;
}
3.3P4315月下“毛景树”
这道题的难点就是边权换点权了,之前我们处理的树链剖分问题都是基于点权解决的,可以注意到树的一个性质,一颗n结点的树,一定是有n-1条边的,那么我们可以不计根结点的点权,将边权下放,即让一条边的边权改为这条边深度更深的结点的点权,注意修改和查询操作中不计最终查询到的根结点即可。如下图所示,改成的有向边的形式即为点权的下放顺序。
此外,分析该题需要维护的操作:1.Change操作:将第k条树枝的权值改为w:由于用链式前向星存图,每次存储两条边,所以实际上是将e[k/2].w修改为w,如果不用链式强向星的话,就在第一次dfs的过程中记录下第i条边记录的结点是v也是可以的,我认为后者是更直白的。然后实际上就是将v这个点进行单点修改即可。
2.Cover操作:区间覆盖,将从u到v的路径上的果子全部改成w个。只要注意好不要计算LCA(u,v)即可,注意线段树部分需要打上cov标记(LCA表示最近公共祖先)。
3.Add操作:区间加,将u,v路径的果子全部加上w。不要操作LCA(u,v)。
4.查询操作:查询u,v的最大值,用线段树维护,不计LCA(u,v)。
注意事项:1.注意懒标记的顺序。覆盖标cov的优先级高于add标记,也就是说当存在cov标记时,要立刻将add清空。
2.覆盖操作修改的值可能为0,所以cov标记要初始化为-1,回收标记也修改成-1。
似乎这道题也没什么别的坑点,为什么我调了一个下午才出来……
已经尽量删调试过程了,没删干净我也没办法
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=1e5+10;
int fa[N],dep[N],top[N],idx[N],sz[N],son[N],nw[N],w[N],cnt,head[N],len,pos[N];
int maxv[N<<2],add[N<<2],cov[N<<2],n,m;
struct edge{
int v,w,i;
};
vector<edge>e[N];
void pushup(int id){
maxv[id]=max(maxv[id<<1],maxv[id<<1|1]);
}
void build(int id,int l,int r){
cov[id]=-1;
if(l==r){
maxv[id]=nw[l];
return;
}
int mid=l+r>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
void pushdown(int id,int l,int r){
if(~cov[id]){
maxv[id<<1]=maxv[id<<1|1]=cov[id];
cov[id<<1]=cov[id<<1|1]=cov[id];
add[id<<1]=add[id<<1|1]=0;
cov[id]=-1;//回收标记
}
add[id<<1]+=add[id];
add[id<<1|1]+=add[id];
maxv[id<<1]+=add[id];
maxv[id<<1|1]+=add[id];
add[id]=0;//回收标记
}
void Cover(int id,int l,int r,int x,int y,int k){//区间覆盖
if(x<=l&&y>=r){
cov[id]=k;
maxv[id]=k;
add[id]=0;
return;
}
pushdown(id,l,r);
int mid=l+r>>1;
if(x<=mid)Cover(id<<1,l,mid,x,y,k);
if(y>mid)Cover(id<<1|1,mid+1,r,x,y,k);
pushup(id);
}
void Add(int id,int l,int r,int x,int y,int k){
if(x<=l&&y>=r){
//cov[id]=k;
maxv[id]+=k;
add[id]+=k;
return;
}
pushdown(id,l,r);
int mid=l+r>>1;
if(x<=mid)Add(id<<1,l,mid,x,y,k);
if(y>mid)Add(id<<1|1,mid+1,r,x,y,k);
pushup(id);
}
int query(int id,int l,int r,int x,int y){
if(x<=l&&y>=r)return maxv[id];
int mid=l+r>>1,ans=0;
pushdown(id,l,r);
if(x<=mid)ans=max(ans,query(id<<1,l,mid,x,y));
if(y>mid)ans=max(ans,query(id<<1|1,mid+1,r,x,y));
return ans;
}
void dfs1(int u,int f){
//第一遍dfs序,主要处理son,dep,top,sz
sz[u]=1;
son[u]=0;
dep[u]=dep[f]+1;
fa[u]=f;
for(edge t:e[u]){
int v=t.v;
int i=t.i;
if(v==f)continue;
dfs1(v,u);
pos[i]=v;
w[v]=t.w;//记录权
sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
void dfs2(int u,int t){
top[u]=t;
idx[u]=++cnt;
nw[cnt]=w[u];
if(!son[u])return;//说明走到了叶子节点
dfs2(son[u],t); //先走重子链
for(edge t:e[u]){
int v=t.v;
if(v==son[u]||v==fa[u])continue;
dfs2(v,v);//走轻子链
}
}
void TrCover(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
Cover(1,1,n,idx[top[u]],idx[u],k);
u=fa[top[u]];
}
if(idx[u]<idx[v])swap(u,v);
Cover(1,1,n,idx[v]+1,idx[u],k);
}
void TrAdd(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
Add(1,1,n,idx[top[u]],idx[u],k);
u=fa[top[u]];
}
if(idx[u]<idx[v])swap(u,v);
Add(1,1,n,idx[v]+1,idx[u],k);
}
int Trquery(int u,int v){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans=max(ans,query(1,1,n,idx[top[u]],idx[u]));
u=fa[top[u]];
}
if(idx[u]<idx[v])swap(u,v);
ans=max(ans,query(1,1,n,idx[v]+1,idx[u]));
return ans;
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
e[u].push_back({v,w,i});
e[v].push_back({u,w,i});
}
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
while(1){
string s;
cin>>s;
if(s=="Stop")break;
else if(s=="Add"){
int u,v,k;
cin>>u>>v>>k;
TrAdd(u,v,k);
}else if(s=="Cover"){
int u,v,k;
cin>>u>>v>>k;
TrCover(u,v,k);
}else if(s=="Change"){
int x,k;
cin>>x>>k;
int v=pos[x];
int u=fa[v];
TrCover(u,v,k);
//Change(1,1,n,pos[x],k);
}else{
int u,v;
//cout<<1;
cin>>u>>v;
cout<<Trquery(u,v)<<endl;
}
}
return 0;
}
3.4P1505旅游
同样的边权化点权,这里不再赘述了。
操作:1:C操作,修改单条边权,同上。
2.N操作:区间取相反数,这里可以思考以下如何放懒标记维护线段树。因为两次取相反数等于不变,可以用异或来维护标记。这道题需要查询最大值,最小值,和。每当进行区间取反操作时,
sum直接取反,max和min分别取反交换即可。
三个查询操作也同上。
注意事项:1.pushdown过程中一定是用异或维护懒标记。
2.在单点修改中,一定要pushdown。因为区间取反后对修改是会有影响的。
这题似乎比P4315容易一些,只是码量长(?
贴个代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
int dep[N],fa[N],sz[N],top[N],son[N],idx[N],w[N],nw[N],head[N],cnt,len,n,m;
struct edge{
int v,w,next;
edge(){}
edge(int a,int b,int c){
v=a,w=b,next=c;//寸图
}
}e[N<<1];
struct node{
int u,v;
}c[N];
void add(int u,int v,int w){
e[len]=edge(v,w,head[u]);
head[u]=len++;
}
int maxv[N<<2],minv[N<<2],sumv[N<<2],tag[N<<2];//tag维护一个相反标
void pushup(int id){
maxv[id]=max(maxv[id<<1],maxv[id<<1|1]);
minv[id]=min(minv[id<<1],minv[id<<1|1]);
sumv[id]= sumv[id<<1]+sumv[id<<1|1];
}
void pushdown(int id,int l,int r){
if(!tag[id])return;
tag[id<<1]^=1;
tag[id<<1|1]^=1;//下传标记
int _max=maxv[id<<1],_min=minv[id<<1];
maxv[id<<1]=-_min;
minv[id<<1]=-_max;
sumv[id<<1]*=-1;
_max=maxv[id<<1|1],_min=minv[id<<1|1 ];
maxv[id<<1|1]=-_min;
minv[id<<1|1]=-_max;
sumv[id<<1|1]*=-1;
tag[id]=0;
}
void build(int id,int l,int r){
if(l==r){
sumv[id]=nw[l];
maxv[id]=nw[l];
minv[id]=nw[l];
return;
}
int mid=l+r>>1;
build(id<<1,l,mid);
build(id<<1|1,mid+1,r);
pushup(id);
}
void Change(int id,int l,int r,int x,int k){//将x点修改为k 单点修改
if(l==r){
sumv[id]=k;
minv[id]=k;
maxv[id]=k;
return;
}
int mid=l+r>>1;
pushdown(id,l,r);
if(x<=mid)Change(id<<1,l,mid,x,k);
else Change(id<<1|1,mid+1,r,x,k);
pushup(id);
}
void Opp(int id,int l,int r,int x,int y){//区间修改
if(x<=l&&y>=r){
int max_=maxv[id],min_=minv[id];
maxv[id]=-min_;
minv[id]=-max_;
sumv[id]*=-1;
tag[id]^=1;
return;
}
int mid=l+r>>1;
pushdown(id,l,r);
if(x<=mid)Opp(id<<1,l,mid,x,y);
if(y>mid)Opp(id<<1|1,mid+1,r,x,y);
pushup(id);
}
int Max(int id,int l,int r,int x,int y){
int ans=-1e9,mid=l+r>>1;
if(x<=l&&y>=r)
return maxv[id];
pushdown(id,l,r);
if(x<=mid)ans=max(ans,Max(id<<1,l,mid,x,y));
if(y>mid)ans=max(ans,Max(id<<1|1,mid+1,r,x,y));
return ans;
}
int Min(int id,int l,int r,int x,int y){
int ans=1e9,mid=l+r>>1;
if(x<=l&&y>=r)
return minv[id];
pushdown(id,l,r);
if(x<=mid)ans=min(ans,Min(id<<1,l,mid,x,y));
if(y>mid)ans=min(ans,Min(id<<1|1,mid+1,r,x,y));
return ans;
}
int Sum(int id,int l,int r,int x,int y){
int ans=0,mid=l+r>>1;
if(x<=l&&y>=r)
return sumv[id];
pushdown(id,l,r);
if(x<=mid)ans+=Sum(id<<1,l,mid,x,y);
if(y>mid)ans+=Sum(id<<1|1,mid+1,r,x,y);
return ans;
}
void dfs(int u,int f){
dep[u]=dep[f]+1;
fa[u]=f;
sz[u]=1;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].v;
if(v==f)continue;
dfs(v,u);
sz[u]+=sz[v];
w[v]=e[i].w;//存边权
if(sz[son[u]]<sz[v])son[u]=v;//更新儿子
}
}
void dfs2(int u,int t){
top[u]=t;
idx[u]=++cnt;
nw[cnt]=w[u];
if(!son[u])return;
dfs2(son[u],t);//访问重子链
for(int i=head[u];~i;i=e[i].next){
int v=e[i].v;
if(v==fa[u]||v==son[u])continue;
dfs2(v,v);//访问轻子链
}
}
void TrSum(int u,int v){
int ans=0;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans+=Sum(1,1,n,idx[top[u]],idx[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
if(u!=v)ans+=Sum(1,1,n,idx[v]+1,idx[u]);
cout<<ans<<endl;
}
void TrMax(int u,int v){
int ans=-1e9;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans=max(ans,Max(1,1,n,idx[top[u]],idx[u]));
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
if(u!=v)ans=max(ans,Max(1,1,n,idx[v]+1,idx[u]));
if(ans!=-1e9)cout<<ans<<endl;
}
void TrMin(int u,int v){
int ans=1e9;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
ans=min(ans,Min(1,1,n,idx[top[u]],idx[u]));
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
if(u!=v)ans=min(ans,Min(1,1,n,idx[v]+1,idx[u]));
if(ans!=1e9)cout<<ans<<endl;
}
void TrOpp(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]])swap(u,v);
Opp(1,1,n,idx[top[u]],idx[u]);
u=fa[top[u]];
}
if(dep[u]<dep[v])swap(u,v);
if(u!=v)Opp(1,1,n,idx[v]+1,idx[u]);
}
int main(){
memset(head,-1,sizeof head);
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v,w;
cin>>u>>v>>w;
u++,v++;
add(u,v,w);
add(v,u,w);
c[i]={u,v};
}
dfs(1,0);
dfs2(1,1);
build(1,1,n);
//for(int i=1;i<=n;i++)cout<<w[i]<<' ';
cin>>m;
for(int i=1;i<=m;i++){
string s;
cin>>s;
if(s=="SUM"){
int u,v;
cin>>u>>v;u++,v++;
TrSum(u,v);
}else if(s=="MAX"){
int u,v;
cin>>u>>v;u++,v++;
TrMax(u,v);
}else if(s=="MIN"){
int u,v;
cin>>u>>v;u++,v++;
TrMin(u,v);
}else if(s=="N"){
int u,v;
cin>>u>>v;
u++,v++;
TrOpp(u,v);
}else{
int i,k;
cin>>i>>k;
int u=c[i].u;
int v=c[i].v;
if(dep[u]<dep[v])swap(v,u);
Change(1,1,n,idx[u],k);
}
}
return 0;
}
哎,感慨一下,还是分块好写啊。