点分治和点分树
模板
- 求树内距离小于等于m的点对数
- 两个点均在某棵子树内
- 一个点在重心
- 两个点属于两棵子树
cpp
int n,m;
vector<PII> adj[N];
bool st[N];
int q[N],p[N];
int get_size(int u,int fa){//获取子树大小
if(st[u])return 0;
int res=1;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
res+=get_size(v,u);
}
return res;
}
int get_wc(int u,int fa,int tot,int &wc){//获取重心(不一定是真重心,满足<=n/2即可)
if(st[u])return 0;
int sum=1,ms=0;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
int t=get_wc(v,u,tot,wc);
ms=max(ms,t);
sum+=t;
}
ms=max(ms,tot-sum);
if(ms<=tot/2)wc=u;
return sum;
}
void get_dist(int u,int fa,int dis,int &qt){//获取子树全部dis
if(st[u])return;
q[qt++]=dis;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
get_dist(v,u,dis+w,qt);
}
}
int get(int a[],int k){//计算
sort(a,a+k);
int res=0;
for(int i=k-1,j=-1;i>=0;i--){
while(j+1<i&&a[j+1]+a[i]<=m)j++;
j=min(j,i-1);
res+=j+1;
}
return res;
}
int cal(int u){
if(st[u])return 0;
int res=0;
get_wc(u,0,get_size(u,0),u);
st[u]=true;
int pt=0;
for(auto [v,w]:adj[u]){
int qt=0;
get_dist(v,0,w,qt);
res-=get(q,qt);//容斥,减去两个点在同一颗子树的情况
for(int k=0;k<qt;k++){
if(q[k]<=m)res++;//重心与子树的点
p[pt++]=q[k];
}
}
res+=get(p,pt);//总计算
for(auto [v,w]:adj[u])res+=cal(v);
return res;
}
void solve(){
cin>>n;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
adj[u].push_back({v,w});
adj[v].push_back({u,w});
}
cin>>m;
cout<<cal(1);
}P4149 [IOI2011] Race
- 求树内点对距离恰好等于m的边数最少数
cpp
int n,m,ans=1e9;
vector<PII> adj[N];
bool st[N];
PII q[N],p[N];
int f[M];
int get_size(int u,int fa){//获取子树大小
if(st[u])return 0;
int res=1;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
res+=get_size(v,u);
}
return res;
}
int get_wc(int u,int fa,int tot,int &wc){//获取重心(不一定是真重心,满足<=n/2即可)
if(st[u])return 0;
int sum=1,ms=0;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
int t=get_wc(v,u,tot,wc);
ms=max(ms,t);
sum+=t;
}
ms=max(ms,tot-sum);
if(ms<=tot/2)wc=u;
return sum;
}
void get_dist(int u,int fa,int dis,int cnt,int &qt){//获取子树全部dis
if(st[u]||dis>m)return;
q[qt++]={dis,cnt};
for(auto [v,w]:adj[u]){
if(v==fa)continue;
get_dist(v,u,dis+w,cnt+1,qt);
}
}
void cal(int u){
if(st[u])return;
int res=0;
get_wc(u,0,get_size(u,0),u);
st[u]=true;
int pt=0;
for(auto [v,w]:adj[u]){
int qt=0;
get_dist(v,0,w,1,qt);
for(int k=0;k<qt;k++){
auto &[dis,cnt]=q[k];
p[pt++]=q[k];
if(dis==m)ans=min(ans,cnt);
ans=min(ans,f[m-dis]+cnt);
}
for(int k=0;k<qt;k++){
auto &[dis,cnt]=q[k];
f[dis]=min(f[dis],cnt);
}
}
for(int k=0;k<pt;k++)f[p[k].first]=1e9;
for(auto [v,w]:adj[u])cal(v);
}
void solve(){
cin>>n>>m;
for(int i=0;i<=1e6;i++)f[i]=1e9;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
adj[u].push_back({v,w});
adj[v].push_back({u,w});
}
cal(1);
if(ans==1e9)ans=-1;
cout<<ans<<"\n";
}P3806 【模板】点分治 1
cpp
int n,m;
vector<PII> adj[N];
bool st[N];
int q[N],p[N];
PII ans[110];
int get_size(int u,int fa){//获取子树大小
if(st[u])return 0;
int res=1;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
res+=get_size(v,u);
}
return res;
}
int get_wc(int u,int fa,int tot,int &wc){//获取重心(不一定是真重心,满足<=n/2即可)
if(st[u])return 0;
int sum=1,ms=0;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
int t=get_wc(v,u,tot,wc);
ms=max(ms,t);
sum+=t;
}
ms=max(ms,tot-sum);
if(ms<=tot/2)wc=u;
return sum;
}
void get_dist(int u,int fa,int dis,int &qt){//获取子树全部dis
if(st[u])return;
q[qt++]=dis;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
get_dist(v,u,dis+w,qt);
}
}
unordered_map<int,bool> f;
void cal(int u){
if(st[u])return;
int res=0;
get_wc(u,0,get_size(u,0),u);
st[u]=true;
int pt=0;
for(auto [v,w]:adj[u]){
int qt=0;
get_dist(v,0,w,qt);
for(int k=0;k<qt;k++){
int dis=q[k];
p[pt++]=q[k];
for(int i=1;i<=m;i++){
auto &[d,res]=ans[i];
if(dis==d)res=1;
if(d-dis>0&&f.count(d-dis))res=1;
}
}
for(int k=0;k<qt;k++){
f[q[k]]=1;
}
}
f.clear();
for(auto [v,w]:adj[u])cal(v);
}
void solve(){
cin>>n>>m;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
adj[u].push_back({v,w});
adj[v].push_back({u,w});
}
for(int i=1;i<=m;i++){
int x;
cin>>x;
ans[i]={x,0};
}
cal(1);
for(int i=1;i<=m;i++){
if(ans[i].second)cout<<"AYE\n";
else cout<<"NAY\n";
}
}P3714(点分治+线段树合并)
给你一棵 个点的无根树。
树上的每条边具有颜色。一共有 种颜色,编号为 到 ,第 种颜色的权值为 。
对于一条树上的简单路径,路径上经过的所有边按顺序组成一个颜色序列,序列可以划分成若干个相同颜色段。定义路径权值为颜色序列上每个同颜色段的颜色权值之和。
请你计算,经过边数在 到 之间的所有简单路径中,路径权值的最大值。
- 可以预处理出lca为当前根的所有dis对应的val值
- 那么对于当前的dis,我们只需要之前的查询最大值,可以用线段树维护
- 但是如果两条路径连接处权值相同,需要减去这个权值
- 因此我们维护两颗线段树,一个表示与当前初始序列颜色不同,一个相同
- 当不同时,将相同的合并到不同的上面
- 记得对于每个邻接表的顶点先按颜色排个序
cpp
vector<int> ls,rs;
vector<int> mx;
int newNode(int x=-INF){
mx.push_back(x);
ls.push_back(0);
rs.push_back(0);
return ls.size()-1;
}
void clear(){
mx.clear();
ls.clear();
rs.clear();
newNode();
}
void pushup(int p){
mx[p]=max(mx[ls[p]],mx[rs[p]]);
}
int merge(int p,int q,int l,int r){
if(!p)return q;
if(!q)return p;
if(l==r){
mx[p]=max(mx[p],mx[q]);
return p;
}
int mid=l+r>>1;
ls[p]=merge(ls[p],ls[q],l,mid);
rs[p]=merge(rs[p],rs[q],mid+1,r);
pushup(p);
return p;
}
int insert(int p,int l,int r,int x,int y){
if(!p)p=newNode();
if(l==r){
mx[p]=max(mx[p],y);
return p;
}
int mid=l+r>>1;
if(x<=mid)ls[p]=insert(ls[p],l,mid,x,y);
else rs[p]=insert(rs[p],mid+1,r,x,y);
pushup(p);
return p;
}
int query(int p,int l,int r,int ql,int qr){
if(!p)return -INF;
if(ql<=l&&r<=qr)return mx[p];
int mid=l+r>>1;
int res=-INF;
if(ql<=mid)res=max(res,query(ls[p],l,mid,ql,qr));
if(qr>mid)res=max(res,query(rs[p],mid+1,r,ql,qr));
return res;
}
vector<PII> adj[N];
bool st[N];
PII q[N];
int c[N];
int get_size(int u,int fa){//获取子树大小
if(st[u])return 0;
int res=1;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
res+=get_size(v,u);
}
return res;
}
int get_wc(int u,int fa,int tot,int &wc){//获取重心(不一定是真重心,满足<=n/2即可)
if(st[u])return 0;
int sum=1,ms=0;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
int t=get_wc(v,u,tot,wc);
ms=max(ms,t);
sum+=t;
}
ms=max(ms,tot-sum);
if(ms<=tot/2)wc=u;
return sum;
}
void get_dist(int u,int fa,int dis,int val,int lstw,int &qt){//获取子树全部dis
if(st[u])return;
q[qt++]={dis,val};
for(auto [v,w]:adj[u]){
if(v==fa)continue;
get_dist(v,u,dis+1,val+(lstw!=w)*c[w],w,qt);
}
}
int n,m,l,r;
int cal(int u){
if(st[u])return -INF;
int res=-INF;
get_wc(u,0,get_size(u,0),u);
st[u]=true;
int pt=0;
clear();
array<int,2> root={0,0};
for(int i=0;i<adj[u].size();i++){
auto [v,w]=adj[u][i];
if(i==0||w!=adj[u][i-1].S){
root[0]=merge(root[0],root[1],1,200000);
root[1]=0;
}
int qt=0;
get_dist(v,0,1,c[w],w,qt);
for(int k=0;k<qt;k++){
auto [dis,val]=q[k];
if(dis>=l&&dis<=r)res=max(res,val);
if(dis>r)continue;
int R=r-dis,L=max(1ll,l-dis);
if(L>R)continue;
res=max(res,query(root[0],1,200000,L,R)+val);
res=max(res,query(root[1],1,200000,L,R)+val-c[w]);
}
for(int k=0;k<qt;k++){
auto [dis,val]=q[k];
if(dis>r)continue;
root[1]=insert(root[1],1,200000,dis,val);
}
}
for(auto [v,w]:adj[u])res=max(res,cal(v));
return res;
}
void solve(){
cin>>n>>m>>l>>r;
for(int i=1;i<=m;i++)cin>>c[i];
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
adj[u].emplace_back(v,w);
adj[v].emplace_back(u,w);
}
for(int i=1;i<=n;i++){
ranges::sort(adj[i],[&](const auto &x,const auto &y){
return x.S<y.S;
});
}
cout<<cal(1)<<"\n";
}点分树模板(P3241 [HNOI2015] 开店)
- 建立新树,存储信息,强制在线询问
cpp
int n,m,A;
vector<PII> adj[N];
bool st[N];
int age[N];
struct Father{
int u,num,dis;
};
vector<Father> f[N];//所有父亲信息,最多有logn个父亲
struct Son{
int age,dis;
bool operator<(const Son&tmp)const{
return age<tmp.age;
}
};
vector<Son> son[N][3];//所有儿子信息
int get_size(int u,int fa){
if(st[u])return 0;
int res=1;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
res+=get_size(v,u);
}
return res;
}
int get_wc(int u,int fa,int tot,int &wc){
if(st[u])return 0;
int sum=1,ms=0;
for(auto [v,w]:adj[u]){
if(v==fa)continue;
int t=get_wc(v,u,tot,wc);
ms=max(ms,t);
sum+=t;
}
ms=max(ms,tot-sum);
if(ms<=tot/2)wc=u;
return sum;
}
void get_dis(int u,int fa,int dis,int wc,int k,vector<Son>&p){
if(st[u])return;
f[u].push_back({wc,k,dis});
p.push_back({age[u],dis});
for(auto [v,w]:adj[u]){
if(v==fa)continue;
get_dis(v,u,dis+w,wc,k,p);
}
}
void cal(int u){
if(st[u])return;
get_wc(u,0,get_size(u,0),u);
st[u]=true;
int k=0;
for(auto [v,w]:adj[u]){
if(st[v])continue;
auto &p=son[u][k];
p.push_back({-1,0}),p.push_back({A+1,0});//哨兵
get_dis(v,0,w,u,k,p);
k++;
sort(p.begin(),p.end());
for(int i=1;i<p.size();i++)p[i].dis+=p[i-1].dis;
}
for(auto [v,w]:adj[u])cal(v);
}
int query(int u,int l,int r){// 询问age在[l,r]的到点u的距离和
int res=0;
for(auto &[fa,num,dis]:f[u]){
int g=age[fa];
if(g>=l&&g<=r)res+=dis;
for(int i=0;i<3;i++){
if(i==num)continue;
auto &p=son[fa][i];
if(p.empty())continue;
int L=lower_bound(p.begin(),p.end(),Son{l,-1})-p.begin();
int R=lower_bound(p.begin(),p.end(),Son{r+1,-1})-p.begin();
res+=dis*(R-L)+p[R-1].dis-p[L-1].dis;
}
}
for(int i=0;i<3;i++){
auto &p=son[u][i];
if(p.empty())continue;
int L=lower_bound(p.begin(),p.end(),Son{l,-1})-p.begin();
int R=lower_bound(p.begin(),p.end(),Son{r+1,-1})-p.begin();
res+=p[R-1].dis-p[L-1].dis;
}
return res;
}
void solve(){
cin>>n>>m>>A;
for(int i=1;i<=n;i++)cin>>age[i];
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
adj[u].push_back({v,w});
adj[v].push_back({u,w});
}
cal(1);
int res=0;
while(m--){
int u,a,b;
cin>>u>>a>>b;
int l=(a+res)%A,r=(b+res)%A;
if(l>r)swap(l,r);
//询问树上年龄在[l,r]的点到u的距离之和
res=query(u,l,r);
cout<<res<<"\n";
}
}P6329 【模板】点分树 | 震波
0 x k求距离x小于等于k的点权和1 x y将val[x]修改成y