点分治,点分树思路
点分治和点分树的核心思路都一样:找到树的重心,以重心为中心,把一棵树拆成若干规模更小的子树,递归处理。
但点分治是一种离线算法,通常用于统计树上满足某种距离条件的点对数量、距离和等问题。
点分树是一种在线算法,其将点分治过程中产生的重心关系保存下来,形成一棵新树,方便在线维护与查询。
点分治
点分治的核心思路就是:求子树重心->处理该重心的答案->递归到下个子树。
如何找树的重心
树的重心:树的重心是指删除该节点后,所有连通子树的节点数都不超过整棵树一半的节点的节点。
所以我们只要有子树大小,就可以很方便维护了。
点击查看代码
void get_sz(int u,int fa){
sz[u]=1;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
get_sz(v,u);
sz[u]+=sz[v];
}
}
int get_heavy_node(int u,int fa,int tot){
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
}
return u;
}
时间复杂度简析
由于树的重心的性质,所以我们最多递归处理 $log(n)$ 层,每层连通块最多 $n$ 个节点,所以点分治本身的复杂度大约是$O(nlog(n))$例题
统计树上距离不超过 \(k\) 的点对
给定一颗 \(n\) 个节点的无根树,对于树中的节点 \(u,v,u \neq v\),求 \(dis(u,v) \le k\) 的点对数。
P3806
P4178
思路
对于每个重心,统计路径过其的点对数,累和即可,记得容斥。
点击查看代码
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=1e5+5;
int head[N],nxt[N],to[N],cnt,val[N];
void add(int u,int v,int w){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
val[cnt]=w;
to[++cnt]=u;
nxt[cnt]=head[v];
head[v]=cnt;
val[cnt]=w;
}
int sz[N];
bool del[N];
ll ans,n,k;
void get_sz(int u,int fa){
sz[u]=1;
for(int i=head[u];i;i=nxt[i]){
if(to[i]==fa||del[to[i]])continue;
get_sz(to[i],u);
sz[u]+=sz[to[i]];
}
}
int get_heavy_node(int u,int fa,int tot){
for(int i=head[u];i;i=nxt[i]){
if(to[i]==fa||del[to[i]])continue;
if(sz[to[i]]>tot/2)return get_heavy_node(to[i],u,tot);
}
return u;
}
void collect_dis(int u,int fa,int disu,vector<int>&dis){//收集距离
dis.push_back(disu);
for(int i=head[u];i;i=nxt[i]){
if(to[i]==fa||del[to[i]])continue;
collect_dis(to[i],u,disu+val[i],dis);
}
}
ll get_ans(vector<int>&dis){//答案计算
sort(dis.begin(),dis.end());
ll res=0;
int l=0,r=dis.size()-1;
while(l<r){
if(dis[l]+dis[r]<=k){
res+=r-l;
l++;
}
else r--;
}
return res;
}
void node_divide(int u){
get_sz(u,-1);
int heavy_node_u=get_heavy_node(u,-1,sz[u]);
del[heavy_node_u]=1;
vector<int>dis;
vector<int>son_tr_dis;
dis.push_back(0);
for(int i=head[heavy_node_u];i;i=nxt[i]){
if(del[to[i]])continue;
son_tr_dis.clear();
collect_dis(to[i],heavy_node_u,val[i],son_tr_dis);
ans-=get_ans(son_tr_dis);//容斥减去儿子节点的答案
dis.insert(dis.end(),son_tr_dis.begin(),son_tr_dis.end());
}
ans+=get_ans(dis);//加上自身答案
for(int i=head[heavy_node_u];i;i=nxt[i]){
if(!del[to[i]])node_divide(to[i]);//分治
}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
}
cin>>k;
node_divide(1);
cout<<ans;
return 0;
}
统计树上距离模 \(3\) 为 \(0\) 的点对数
给定一颗 \(n\) 个节点的无根树,对于树中的节点 \(u,v,u \neq v\),求 \(dis(u,v)=0(mod 3)\) 的点对数。
每个结点记录模 \(3\) 的路径数即可。
P2634
点击查看代码
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=1e5+5;
ll head[N],to[N],nxt[N],cnt,val[N],t[3],f[3],num,ans;
void add(int u,int v,int w){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
val[cnt]=w;
to[++cnt]=u;
nxt[cnt]=head[v];
head[v]=cnt;
val[cnt]=w;
}
ll sz[N],del[N],n;
void get_sz(int u,int fa){
sz[u]=1;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
get_sz(v,u);
sz[u]+=sz[v];
}
}
int get_heavy_node(int u,int fa,int tot){
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
}
return u;
}
void get_dis(int u,int fa,int dis){
t[dis]++;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(del[v]||v==fa)continue;
get_dis(v,u,(dis+val[i])%3);
}
}
void node_divide(int u){
get_sz(u,-1);
int hnu=get_heavy_node(u,-1,sz[u]);
del[hnu]=1;
memset(f,0,sizeof(f));
f[0]=1;
for(int i=head[hnu],v;i;i=nxt[i]){
v=to[i];
if(del[v])continue;
memset(t,0,sizeof(t));
get_dis(v,hnu,val[i]%3);
ans+=f[0]*t[0]+f[1]*t[2]+f[2]*t[1];
f[0]+=t[0];
f[1]+=t[1];
f[2]+=t[2];
}
for(int i=head[hnu],v;i;i=nxt[i]){
v=to[i];
if(del[v])continue;
node_divide(v);
}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1,u,v,w;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
}
node_divide(1);
num=n*n;//总方案数
ans*=2;//无序点对
ans+=n;//本身
ll gcdd=__gcd(ans,num);
cout<<ans/gcdd<<'/'<<num/gcdd;
return 0;
}
点分树
点分树就是点分治把重心拎出来建一棵树,方便动态修改。
例题
求带修改点权时,距离某点距离不超过 \(k\) 的点权和。
思路
配上树状数组即可方便修改。
点击查看代码
#include<bits/extc++.h>
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
#define lowbit(x) ((x)&(-(x)))
const int N=3e5+5;
struct QwQ{
int first,second,third;
};
struct BIT{
vector<int>dis,tr;
void init(){
sort(dis.begin(),dis.end());
dis.erase(unique(dis.begin(),dis.end()),dis.end());
tr.assign(dis.size()+1,0);
}
void add(int u,int v){
int pos=lower_bound(dis.begin(),dis.end(),u)-dis.begin()+1;
for(;pos<(int)tr.size();pos+=lowbit(pos)){
tr[pos]+=v;
}
}
int que(int u){
int pos=upper_bound(dis.begin(),dis.end(),u)-dis.begin();
int res=0;
for(;pos;pos-=lowbit(pos)){
res+=tr[pos];
}
return res;
}
}all[N],sub[N];//all是所有节点到重心的点权,sub是重心的分支内到重心的点权。
vector<QwQ>path[N];
int head[N],to[N],nxt[N],cnt;
int sz[N],dep[N],del[N],up[N][30],fa[N],tot;
int n,m,val[N],lastans;
void add(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
to[++cnt]=u;
nxt[cnt]=head[v];
head[v]=cnt;
}
void get_sz(int u,int fa){
sz[u]=1;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
get_sz(v,u);
sz[u]+=sz[v];
}
}
int get_heavy_node(int u,int fa,int tot){
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
}
return u;
}
void get_path(int u,int fa,int node,int dis,int way){
path[u].push_back({node,dis,way});
all[node].dis.push_back(dis);
sub[way].dis.push_back(dis);
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v==fa||del[v])continue;
get_path(v,u,node,dis+1,way);
}
}//预处理出来 dis,way一系列信息
void build(int u,int fa_){
get_sz(u,0);
int h_n_u=get_heavy_node(u,0,sz[u]);
del[h_n_u]=1;
fa[h_n_u]=fa_;
path[h_n_u].push_back({h_n_u,0,0});
all[h_n_u].dis.push_back(0);
for(int i=head[h_n_u],v;i;i=nxt[i]){
v=to[i];
if(del[v])continue;
get_path(v,h_n_u,h_n_u,1,++tot);
}
for(int i=head[h_n_u],v;i;i=nxt[i]){
v=to[i];
if(v==fa_||del[v])continue;
build(v,h_n_u);
}
}//建树
void dfs_lca(int u,int fa){
dep[u]=dep[fa]+1;
up[u][0]=fa;
for(int i=1;i<=25;i++)up[u][i]=up[up[u][i-1]][i-1];
for(int i=head[u],v;i;i=nxt[i]){v=to[i];if(v!=fa)dfs_lca(v,u);}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int dis=dep[u]-dep[v];
for(int i=0;i<=25;i++){
if((dis>>i)&1)u=up[u][i];
}
if(u==v)return u;
for(int i=25;i>=0;i++){
if(up[u][i]!=up[v][i]){
u=up[u][i];
v=up[v][i];
}
}
return up[u][0];
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd(int u,int v){
for(auto [node,dis1,way]:path[u]){
all[node].add(dis1,v);
if(way)sub[way].add(dis1,v);
}
}
int que(int u,int k){
int ans=0;
for(auto[node,dis1,way]:path[u]){
if(dis1>k)continue;
ans+=all[node].que(k-dis1);
if(way)ans-=sub[way].que(k-dis1);
}
return ans;
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>val[i];
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
add(u,v);
}
dfs_lca(1,0);
build(1,0);
for(int i=1;i<=n;i++)all[i].init();
for(int i=1;i<=tot;i++)sub[i].init();
for(int i=1;i<=n;i++){
upd(i,val[i]);
}
for(int i=1,op,k,x;i<=m;i++){
cin>>op>>x>>k;
x^=lastans;
k^=lastans;
if(op==0){
lastans=que(x,k);
cout<<lastans<<'\n';
}
else{
upd(x,k-val[x]);
val[x]=k;
}
}
return 0;
}
求所有标记点中,最远的两点距离。
P2056
思路
非常简单哈,维护距离即可,要可修改,可删除,用multiset即可。
点击查看代码
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
using pii=pair<int,int>;
using pll=pair<long long,long long>;
using ll=long long;
using ull=unsigned long long;
using ld=long double;
using i128=__int128;
#define fi first
#define se second
const int N=2e5+5;
int head[N],to[N],nxt[N],cnt;
int fa[N],up[N][30],sz[N],dep[N],del[N];
int n,q,num,val[N];
bool co[N];
vector<pii>path[N];
char op;
multiset<int>pq[N],pq2[N],ans;//pq到其父亲fa的距离,pq2表示他所有分支的pq中的最大值,便于容斥,ans是所有答案的集合
void add(int u,int v){
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
to[++cnt]=u;
nxt[cnt]=head[v];
head[v]=cnt;
}
void get_sz(int u,int fa){
sz[u]=1;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(fa==v||del[v])continue;
get_sz(v,u);
sz[u]+=sz[v];
}
}
int get_heavy_node(int u,int fa,int tot){
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(to[i]==fa||del[v])continue;
if(sz[v]>tot/2)return get_heavy_node(v,u,tot);
}
return u;
}
void get_path(int u,int fa,int c,int d){
path[u].push_back({c,d});
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(fa==v||del[v])continue;
get_path(v,u,c,d+1);
}
}
void build(int u,int fa_){
get_sz(u,0);
int heavy_node_u=get_heavy_node(u,0,sz[u]);
fa[heavy_node_u]=fa_;
get_path(heavy_node_u,0,heavy_node_u,0);
del[heavy_node_u]=1;
for(int i=head[heavy_node_u],v;i;i=nxt[i]){
v=to[i];
if(!del[v])build(v,heavy_node_u);
}
}
void dfs_lca(int u,int fa){
dep[u]=dep[fa]+1;
up[u][0]=fa;
for(int i=1;i<=25;i++)up[u][i]=up[up[u][i-1]][i-1];
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];
if(v!=fa)dfs_lca(v,u);
}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int dis=dep[u]-dep[v];
for(int i=0;i<=25;i++){
if(dis&1)u=up[u][i];
dis>>=1;
}
if(u==v)return u;
for(int i=25;i>=0;i--){
if(up[u][i]!=up[v][i]){
u=up[u][i];
v=up[v][i];
}
}
return up[u][0];
}
int dis(int u,int v){
return dep[u]+dep[v]-2*dep[lca(u,v)];
}
void upd_ans(int x){
int old=val[x];
if(old!=-1)ans.erase(ans.lower_bound(old));
if(pq2[x].size()==0||pq2[x].size()==1)val[x]=-1;
else{
auto it=pq2[x].rbegin();
val[x]=*it;
it++;
val[x]+=*it;
}
if(val[x]!=-1)ans.insert(val[x]);
}
void add_node(int x){
co[x]=1;
num++;
int pre=0;
for(auto it=path[x].rbegin();it!=path[x].rend();++it){
int i=it->first,d=it->second;
if(!pre){
pq2[i].insert(0);
upd_ans(i);
}else{
int old_top=-1,new_top=-1;
if(!pq[pre].empty())old_top=*pq[pre].rbegin();
pq[pre].insert(d);
if(!pq[pre].empty())new_top=*pq[pre].rbegin();
if(old_top!=new_top){
if(old_top!=-1){
auto jt=pq2[i].find(old_top);
if(jt!=pq2[i].end())pq2[i].erase(jt);
}
if(new_top!=-1)pq2[i].insert(new_top);
upd_ans(i);
}
}
pre=i;
}
}
void del_node(int x){
co[x]=0;
num--;
int pre=0;
for(auto it=path[x].rbegin();it!=path[x].rend();++it){
int i=it->first,d=it->second;
if(!pre){
auto jt=pq2[i].find(0);
if(jt!=pq2[i].end())pq2[i].erase(jt);
upd_ans(i);
}else{
int old_top=-1,new_top=-1;
if(!pq[pre].empty())old_top=*pq[pre].rbegin();
auto jt=pq[pre].find(d);
if(jt!=pq[pre].end())pq[pre].erase(jt);
if(!pq[pre].empty())new_top=*pq[pre].rbegin();
if(old_top!=new_top){
if(old_top!=-1){
auto kt=pq2[i].find(old_top);
if(kt!=pq2[i].end())pq2[i].erase(kt);
}
if(new_top!=-1)pq2[i].insert(new_top);
upd_ans(i);
}
}
pre=i;
}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
memset(val,-1,sizeof(val));
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
add(u,v);
}
dfs_lca(1,0);
build(1,0);
for(int i=1;i<=n;i++)add_node(i);
cin>>q;
for(int i=1,x;i<=q;i++){
cin>>op;
if(op=='C'){
cin>>x;
if(co[x])del_node(x);
else add_node(x);
}
else{
if(num==0)cout<<"-1\n";
else if(num==1)cout<<"0\n";
else cout<<*ans.rbegin()<<'\n';
}
}
return 0;
}

浙公网安备 33010602011771号