文章目录
- 前言
- 点分治
- 背景
- 解析
- 代码
- 点分树
- 情境
- 代码
- thanks for reading!
所谓点分治,就是把所有的点分开来治
(逃)
(应广大观众要求,开篇废话改回原风格qwq)
前言
很神奇的算法。
没有引入任何新的知识,却把时间复杂度降到了一个很好的等级。
感觉点分治模板本身当成一道题来做也不过分。
对于点分树,感觉它的另一个名字也很不错:动态点分治。
通常可以解决一些与树原形态联系较小的问题(如距离等)。
点分治
背景
给定一棵树,求树上距离不超过k的点对数目
n≤105n\leq10^5n≤105
传送门
解析
考虑点分治
分治的普遍特征是把总问题分解成若干子问题递归求解,再合并答案
本题中,不难想到按照重心把子树分成若干个联通块进行递归,这样划分次数不会超过log级别
现在的重点就是,如何合并答案
首先各个联通块的答案肯定要加起来
剩下还没统计到的就是 经过重心(设为x) 的合法路径了
考虑把所有点dfs一遍算出深度,sort一下后利用双指针就可以线性求出来
比较敏锐的童鞋很显然会发现我这纯粹就是胡说八道,因为这样还会重复统计到在同一个子树内的答案
所以我们要把在同一个子树内的答案容斥掉
代码实现上,计算答案和容斥部分可以通过传参的不同用一个函数进行
具体实现很优雅,看代码应该能更好的理解
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=4e4+100;
const int M=2e5+10500;
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n,m;
struct node{int to,nxt,w;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y,int w){p[++cnt]=(node){y,fi[x],w};fi[x]=cnt;return;
}int siz[N],mx[N],dis[N],tot,rt,S,ans;
bool vis[N];void find(int x,int fa){siz[x]=1;mx[x]=0;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]||to==fa) continue;find(to,x);siz[x]+=siz[to];mx[x]=max(mx[x],siz[to]);}mx[x]=max(mx[x],S-siz[x]);if(!rt||mx[x]<mx[rt]) rt=x;return;
}void getdis(int x,int fa,int d){dis[++tot]=d;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==fa||vis[to]) continue;getdis(to,x,d+p[i].w);}return;
}int calc(int x,int d){tot=0;getdis(x,0,d); sort(dis+1,dis+1+tot);int l=1,r=tot,res(0);while(l<r){if(dis[l]+dis[r]<=m) res+=r-l,l++;else --r;}return res;
}void solve(int x){S=siz[x];rt=0;find(x,0);x=rt;ans+=calc(x,0);vis[x]=1;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;ans-=calc(to,p[i].w);}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;solve(to);}return;
}int main(){
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();for(int i=1;i<n;i++){int x=read(),y=read(),w=read();addline(x,y,w);addline(y,x,w);}m=read();S=n;find(1,0);siz[rt]=n;solve(rt);printf("%d\n",ans);return 0;
}
/*
4 4 1 2 3 4 7
*/
点分树
情境
给出一棵点带权边无权的树,要求你支持在线回答到某个点距离不超过 kkk 的点对权值和,或者修改某个点对权值。
其实所有的分治算法都形成了一个树形结构,那么这里我们考虑把之前的点分治的结构显性的建出来。
具体的,每一层的重心都是下一层重心的父亲。
这样我们就建出来点分树,它的树高是 O(logn)O(\log n)O(logn) 的。
对于每次对 xxx 结点的询问,我们就先统计 xxx 自己处的信息(距离不超过 kkk 的权值和),然后不停往上跳 fafafa,然后统计 fafafa 的信息(与 fafafa 距离不超过 k−dis(fa,x)k-dis(fa,x)k−dis(fa,x) 的权值和)。
那么对应的,我们就需要每个结点维护两个数据结构:一个维护自己的信息,一个维护对父亲的贡献。(这是一个很重要的套路!)
具体的说,维护两个树状数组,一个维护点分树上的子树内到自己距离为 xxx 的权值和,一个维护点分树上的子树内到父亲距离为 xxx 的权值和。
然而,我们是无法对每个结点开大小为 O(n)O(n)O(n) 的数组的,所以我们考虑使用 vector
,只开到 O(sizx)O(siz_x)O(sizx) 大小,这样空间复杂度就是 O(nlogn)O(n\log n)O(nlogn) 的。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define OK printf("ok\n")
#define debug(...) fprintf(stderr,__VA_ARGS__)
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}
const int N=2e5+100;
const int inf=1e9+100;
int n,m;
struct node{int to,nxt;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y){p[++cnt]=(node){y,fi[x]};fi[x]=cnt;return;
}
int dep[N],q[N<<1],tot,pl[N];
void dfs0(int x,int f){dep[x]=dep[f]+1;q[++tot]=x;pl[x]=tot;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f) continue;dfs0(to,x);q[++tot]=x;}return;
}
int Min(int x,int y){return dep[x]<dep[y]?x:y;
}
int lg[N<<1],mn[N<<1][20],mi[20];
void init(){dfs0(1,0);lg[0]=-1;for(int i=1;i<=tot;i++) lg[i]=lg[i>>1]+1;mi[0]=1;for(int i=1;i<=lg[tot];i++) mi[i]=mi[i-1]<<1;for(int i=1;i<=tot;i++) mn[i][0]=q[i];for(int k=1;k<=lg[tot];k++){for(int i=1;i+mi[k]-1<=tot;i++) mn[i][k]=Min(mn[i][k-1],mn[i+mi[k-1]][k-1]);}return;
}
inline int Lca(int x,int y){x=pl[x];y=pl[y];if(x>y) swap(x,y);int k=lg[y-x+1];//printf(" k=%d\n",k);return Min(mn[x][k],mn[y-mi[k]+1][k]);
}
inline int Dis(int x,int y){int lca=Lca(x,y);//printf("(%d %d) lca=%d dis=%d\n",x,y,lca,dep[x]+dep[y]-2*dep[lca]);return dep[x]+dep[y]-2*dep[lca];
}
vector<int>f[2][N];
int w[N],top[N];
int rt,mnn,siz[N],S,son[N];
bool vis[N];
int o;
void findrt(int x,int f){siz[x]=1;son[x]=0;for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f||vis[to]) continue;findrt(to,x);siz[x]+=siz[to];son[x]=max(son[x],siz[to]);}son[x]=max(son[x],S-siz[x]);if(mnn>son[x]){mnn=son[x];rt=x;}return;
}
int tim;
int solve(int x,int nS){//if(tim%1000==0) debug("%d\n",x);//printf("??\n");S=nS;mnn=inf;findrt(x,0);x=rt;vis[x]=1;siz[x]=S+1;f[0][x].resize(siz[x]+1);f[1][x].resize(siz[x]+1);for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(vis[to]) continue;int tmp=solve(to,nS-son[to]);top[tmp]=x;}return x;
}
void add(int op,int x,int p,int w){++p;for(;p<=siz[x];p+=p&-p){f[op][x][p]+=w;//printf("%d\n",p);}return;
}
int ask(int op,int x,int p){if(p<0) return 0;++p;p=min(p,siz[x]);int res(0);for(;p;p-=p&-p) res+=f[op][x][p];return res;
}
void upd(int x,int w){//w:deltafor(int i=x;i;i=top[i]) add(0,i,Dis(x,i),w);for(int i=x;top[i];i=top[i]) add(1,i,Dis(x,top[i]),w);return;
}
int query(int x,int d){int ans=ask(0,x,d);//printf("ans=%d\n",ans);for(int i=x;top[i];i=top[i]){int nd=d-Dis(x,top[i]);//printf("top=%d nd=%d ans+=%d-%d=%d\n",top[x],)ans+=ask(0,top[i],nd)-ask(1,i,nd);}return ans;
}
signed main(){
#ifndef ONLINE_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout);
#endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();m=read();for(int i=1;i<=n;i++) w[i]=read();for(int i=1;i<n;i++){int x=read(),y=read();addline(x,y);addline(y,x);}init(); solve(1,n);/*mnn=inf;S=n;findrt(1,0);mnn=0;findrt(rt,0); solve(rt);*/ //return 0;for(int i=1;i<=n;i++) upd(i,w[i]);//for(int i=1;i<=n;i++) printf("i=%d siz=%d top=%d\n",i,siz[i],top[i]);int lst(0);for(int i=1;i<=m;i++){int op=read(),x=read()^lst,y=read()^lst;if(op==1){upd(x,y-w[x]);w[x]=y;}else{printf("%d\n",lst=query(x,y));}}return 0;
}
/*
7 1
1 2 3 4 5 6 7
1 2
2 3
2 4
4 5
4 6
1 7
0 7 1
*/