点分治学习笔记
一、概述
前置知识:树的重心。
1. 经典应用 1
假设我们要统计一棵有
一个朴素的算法是遍历树上的所有点对,处理出距离(也就是链的长度)。
时间复杂度
考虑优化。由于只有一次查询,直接求出所有的点对距离太没有必要了。
所以考虑进行分治。
算法流程如下:
-
选择树的重心
,它将整棵树分成若干部分,计算所有链中两个端点分别在 的两棵不同子树的贡献。- 具体来说,遍历
的所有子树,开 2 个桶 和 , 负责装 当前子树里,一个端点为 的长度为 的链有多少条。 负责装 当前子树之前遍历过的所有子树里,一个端点为 的长度为 的链有多少条。 - 初始时
为 1,因为可能有一些长度为 的链其中一个端点就是 。 - 处理当前子树时,我们枚举
里的每一个不为 的值,其下标为 ,那么答案要加上 。 - 当我们处理完当前子树后,将
桶倒进 桶里,并且将 桶清空。这样可以做到不重不漏。
- 具体来说,遍历
-
删掉点
,继续递归地考虑 的所有子树。
正确性是因为一个在经过其中一级重心的链,不会在上级重心中被算过,也不会在下级重心再被算到。
让我们来分析一下时间复杂度。
首先,由于重心的性质,每次删除全树的重心,最大的子树大小至多为原来的一半。所以一棵有
所以如果我们可以实现对于每一棵大小为
处理桶
那么我们就得到了一个
加强版:如果这一题的 gp_hash_table
或 cc_hash_table
(pbds)代替数组作为桶 set
或者 map
的话会多一个
2. 经典应用 2
假设我们要统计一棵有
注意到这一题和上一题类似,我们照样点分治就可以了,但是在计算贡献的时候出了一些问题。
我们是要求长度小于等于
- 用一个数组记录
的子树内的所有点距离 的距离,然后排个序,用双指针法计算出所有方法数之和。 - 以上计算可能会统计一些不可能的贡献,比如链的两个的端点在
的同一子树内,所以我们枚举 的所有子节点 ,再用答案减去关于 的上述结果。
这样不用考虑
有一些细节,代码实现详见下文。
二、例题
1. 【模板】点分治1
注意这一题有多次询问,而且每次找到的重心都完全相同,所以可以递归一次,一起处理。
因为这题只考虑可行性,所以用 C++ 20 的 unordered_set
作为桶。代码不到 1.2 kb。
点击查看代码
#include<bits/stdc++.h>
#include<unordered_set>
using namespace std;
typedef long long ll;
const ll o=20010;
unordered_set<ll>C,D;
ll n,m,c=0,r,a[o],q[o],nxt[o],h[o],t[o],v[o],s[o],p[o],d[o];
inline void add(ll x,ll y,ll z){
nxt[++c]=h[x];h[x]=c;t[c]=y;v[c]=z;
}
void R(ll x,ll f,ll T){
s[x]=1;p[x]=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)R(y,x,T),s[x]+=s[y],p[x]=max(p[x],s[y]);
if((p[x]=max(p[x],T-s[x]))<p[r])r=x;
}
void W(ll x,ll f,ll ds){
D.insert(ds);
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)W(y,x,ds+v[i]);
}
void Q(ll x,ll f){
C.insert(0);d[x]=1;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f){
W(y,x,v[i]);
for(auto j:D)
for(ll k=1;k<=m;k++)
if(q[k]>=j)a[k]|=C.contains(q[k]-j);
for(auto j:D)C.insert(j);
D.clear();
}
C.clear();
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)p[r=0]=1e9,R(y,x,s[y]),R(r,0,s[y]),Q(r,x);
}
int main(){
scanf("%lld%lld",&n,&m);
for(ll i=1,x,y,z;i<n;i++){
scanf("%lld%lld%lld",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
for(ll i=1;i<=m;i++)scanf("%lld",&q[i]);
p[r=0]=1e9;R(1,0,n);R(r,0,n);Q(r,0);
for(ll i=1;i<=m;i++)puts(a[i]?"AYE":"NAY");
return 0;
}
2. Tree
这就是经典应用 2。所以可以用容斥。
但是我做这道题的时候 Too young too simple,sometimes naive,所以手写了一个平衡树。时间复杂度也是
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll o=80010;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=0;ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return f?x:-x;
}
struct FHQTreap{
struct bst{ll l,r,s,v,p;}t[o];
ll rt=0,cnt=0;
inline void C(){for(ll i=1;i<=cnt;i++)t[i]=bst{0,0,0,0,0};rt=cnt=0;}
inline void N(ll x){t[++cnt]=bst{0,0,1,x,rand()};}
inline void U(ll x){t[x].s=t[t[x].l].s+t[t[x].r].s+1;}
inline void S(ll x,ll k,ll &l,ll &r){
if(!x){l=r=0;return;}
if(t[x].v<=k){l=x;S(t[x].r,k,t[x].r,r);}
else{r=x;S(t[x].l,k,l,t[x].l);}
U(x);
}
inline ll M(ll l,ll r){
if(!l||!r)return l+r;
if(t[l].p<=t[r].p){t[l].r=M(t[l].r,r);U(l);return l;}
else{t[r].l=M(l,t[r].l);U(r);return r;}
}
inline void I(ll x){ll l,r;S(rt,x,l,r);N(x);rt=M(M(l,cnt),r);}
inline ll G(ll x){ll l,r,p=0;S(rt,x,l,r);p=t[l].s;rt=M(l,r);return p;}
}C,D;
ll n,m,c=0,r,nxt[o],h[o],t[o],v[o],s[o],p[o],d[o],q,ans=0;
inline void add(ll x,ll y,ll z){nxt[++c]=h[x];h[x]=c;t[c]=y;v[c]=z;}
inline void R(ll x,ll f,ll T){
s[x]=1;p[x]=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)R(y,x,T),s[x]+=s[y],p[x]=max(p[x],s[y]);
if(p[r]>(p[x]=max(p[x],T-s[x])))r=x;
}
inline void W(ll x,ll f,ll Z){
D.I(Z);
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)W(y,x,Z+v[i]);
}
inline void Q(ll x,ll f){
C.I(0);d[x]=1;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f){
W(y,x,v[i]);
for(ll j=1;j<=D.cnt;j++)
if(q>=D.t[j].v)ans+=C.G(q-D.t[j].v);
for(ll j=1;j<=D.cnt;j++)C.I(D.t[j].v);
D.C();
}
C.C();
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)p[r=0]=1e9,R(y,x,s[y]),Q(r,x);
}
int main(){
srand(time(0));n=read();
for(ll i=1,x,y,z;i<n;i++){
x=read();y=read();z=read();
add(x,y,z);add(y,x,z);
}
q=read();p[r=0]=1e9;R(1,0,n);Q(r,0);cout<<ans<<'\n';
return 0;
}
容斥写法
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int _=80010;
int cnt=0,nxt[_],to[_],v[_],h[_],ans=0,n,k,del[_],sz[_],p[_],dis[_],rt;
inline void add(int x,int y,int z){
nxt[++cnt]=h[x];to[cnt]=y;v[cnt]=z;h[x]=cnt;
}
inline void getroot(int x,int fa,int tot){
sz[x]=1;p[x]=0;
for(int i=h[x],y;i;i=nxt[i])
if(!del[y=to[i]]&&y!=fa){
getroot(y,x,tot);
sz[x]+=sz[y];
p[x]=max(p[x],sz[y]);
}
p[x]=max(p[x],tot-sz[x]);
if(p[x]<p[rt])rt=x;
}
inline void getdis(int x,int fa,int dist){
dis[++dis[0]]=dist;
for(int i=h[x],y;i;i=nxt[i])
if(!del[y=to[i]]&&y!=fa)getdis(y,x,dist+v[i]);
}
inline int cal(int x,int dist){
dis[0]=0;
getdis(x,0,dist);
sort(dis+1,dis+dis[0]+1);
int res=0,l=1,r=dis[0];
while(l<r)
if(dis[l]+dis[r]<=k)res+=r-l,l++;
else r--;
return res;
}
inline void solve(int x){
del[x]=1;
ans+=cal(x,0);
for(int i=h[x],y;i;i=nxt[i])
if(!del[y=to[i]]){
ans-=cal(y,v[i]);
rt=0;
getroot(y,x,sz[y]);
solve(rt);
}
}
int main(){
scanf("%d",&n);
for(int i=1,x,y,z;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);add(y,x,z);
}
scanf("%d",&k);
p[rt=0]=1e9;
getroot(1,0,n);
solve(rt);
printf("%d\n",ans);
return 0;
}
3. [国家集训队]聪聪可可
我们维护两个大小为 3 的桶,
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll o=80010;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=0;ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return f?x:-x;
}
ll nxt[o],h[o],t[o],v[o],d[o],s[o],p[o],C[3],D[3],n,c=0,r,ans=0;
inline void out(ll x){
ll X=x*2+n,Y=n*n,G=__gcd(X,Y);
cout<<X/G<<'/'<<Y/G<<'\n';
}
inline void add(ll x,ll y,ll z){nxt[++c]=h[x];h[x]=c;t[c]=y;v[c]=z;}
inline void R(ll x,ll f,ll T){
s[x]=1;p[x]=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)R(y,x,T),s[x]+=s[y],p[x]=max(p[x],s[y]);
if(p[r]>(p[x]=max(p[x],T-s[x])))r=x;
}
inline void W(ll x,ll f,ll T){
D[T%3]++;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)W(y,x,T+v[i]);
}
inline void Q(ll x,ll f){
d[x]=1;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f){
W(y,x,v[i]);
ans+=D[0]+D[0]*C[0]+D[1]*C[2]+D[2]*C[1];
for(ll j=0;j<3;j++)C[j]+=D[j],D[j]=0;
}
C[0]=C[1]=C[2]=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)p[r=0]=1e9,R(y,x,s[y]),Q(r,x);
}
int main(){
n=read();
for(ll i=1,x,y,z;i<n;i++){
x=read();y=read();z=read();
add(x,y,z);add(y,x,z);
}
p[r=0]=1e9;R(1,0,n);Q(r,0);
out(ans);
return 0;
}
4. [IOI2011]Race
这一题一看就是点分治,但是怎么维护呢?首先
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll o=400010,z=1000010,I=1000000000;
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=0;ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return f?x:-x;
}
ll n,k,S,K,A=I,c=0,r,p[o],nxt[o],h[o],t[o],v[o],s[o],d[o],C[z],D[z],E[o];
inline void add(ll x,ll y,ll z){nxt[++c]=h[x];h[x]=c;t[c]=y;v[c]=z;}
inline void R(ll x,ll f,ll T){
s[x]=1;p[x]=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)R(y,x,T),s[x]+=s[y],p[x]=max(p[x],s[y]);
if(p[r]>(p[x]=max(p[x],T-s[x])))r=x;
}
inline void W(ll x,ll f,ll T,ll N){
if(T>k)return;
E[++K]=T;D[T]=min(D[T],N);
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f&&T+v[i]<=k)W(y,x,T+v[i],N+1);
}
inline void Q(ll x,ll f){
d[x]=1;S=K=0;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f&&v[i]<=k){
W(y,x,v[i],1);
for(ll j=S+1;j<=K;j++)
if(E[j]<=k)A=min(A,D[E[j]]+C[k-E[j]]);
for(ll j=S+1;j<=K;j++)C[E[j]]=min(C[E[j]],D[E[j]]),D[E[j]]=I;
S=K;
}
for(ll i=1;i<=K;i++)C[E[i]]=I;
for(ll i=h[x],y;i;i=nxt[i])
if(!d[y=t[i]]&&y!=f)p[r=0]=I,R(y,x,s[y]),Q(r,x);
}
int main(){
n=read();k=read();
for(ll i=1,x,y,z;i<n;i++){
x=read()+1;y=read()+1;z=read();
add(x,y,z);add(y,x,z);
}
for(ll i=1;i<=k;i++)C[i]=D[i]=I;
p[r=0]=I;R(1,0,n);Q(r,0);
cout<<(A>=I?-1:A)<<'\n';
return 0;
}
5. 2013ACM/ICPC亚洲区南京站现场赛 D Tree
这一题就是要求树上乘积为
首先注意到模数 int
范围内,要开 long long
。
然后所有点权都小于
所以我们就可以求出
由于这道题是点有权值,所以在计算一个点
要求字典序最小,那么在乘积的同等条件下,两个端点都要尽量小。那么桶一开始要赋值无穷大。
这里用
最后记得统计的时候要加上
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll _=100010,mod=1000003,I=1000000000;
inline ll read(){
ll x=0;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch==EOF)exit(0);ch=getchar();}
while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x;
}
inline void print(ll x){
if(x>=10)print(x/10);
putchar(x%10+48);
}
inline void write(ll x){
if(x<0)putchar('-'),x=-x;
print(x);
putchar(10);
}
inline ll ksm(ll a,ll b){
ll r=1;
while(b){if(b&1)r=r*a%mod;a=a*a%mod;b>>=1;}
return r;
}
ll s[_],p[_],c[mod],d[mod],n,k,r,q[mod],z[_],a[_],C[_],D[_];
pair<ll,ll>ans;
vector<ll>v[_];
inline pair<ll,ll>MIN(pair<ll,ll>x,pair<ll,ll>y){
if(x.first>x.second)swap(x.first,x.second);
if(y.first>y.second)swap(y.first,y.second);
return x<y?x:y;
}
inline void R(ll x,ll f,ll T){
s[x]=1;p[x]=0;
for(ll i=0,y;i<v[x].size();i++)
if(!z[y=v[x][i]]&&y!=f)R(y,x,T),s[x]+=s[y],p[x]=max(p[x],s[y]);
if((p[x]=max(p[x],T-s[x]))<p[r])r=x;
}
inline void W(ll x,ll f,ll T){
D[++D[0]]=T;d[T]=min(d[T],x);
for(ll i=0,y;i<v[x].size();i++)
if(!z[y=v[x][i]]&&y!=f)W(y,x,T*a[y]%mod);
}
inline void S(ll x,ll f){
z[x]=1;C[++C[0]]=1;c[1]=x;
for(ll i=0,y;i<v[x].size();i++)
if(!z[y=v[x][i]]&&y!=f){
W(y,x,a[y]);
for(ll j=1;j<=D[0];j++)
if(c[k*q[D[j]*a[x]%mod]%mod]<I)ans=MIN(ans,make_pair(d[D[j]],c[k*q[D[j]*a[x]%mod]%mod]));
for(ll j=1;j<=D[0];j++)C[++C[0]]=D[j],c[D[j]]=min(c[D[j]],d[D[j]]),d[D[j]]=I;
D[0]=0;
}
for(ll i=1;i<=C[0];i++)c[C[i]]=I;
C[0]=0;
for(ll i=0,y;i<v[x].size();i++)
if(!z[y=v[x][i]]&&y!=f)r=0,R(y,x,s[y]),R(r,0,s[y]),S(r,x);
}
int main(){
for(ll i=1;i<mod;i++)q[i]=ksm(i,mod-2),c[i]=d[i]=I;
while(1){
n=read();k=read();
for(ll i=1;i<=n;i++)a[i]=read(),v[i].clear(),z[i]=0;
for(ll i=1,x,y;i<n;i++){
x=read();y=read();
v[x].push_back(y);
v[y].push_back(x);
}
if(!k){puts("No solution");continue;}
ans=make_pair(I,I);p[r=0]=I;R(1,0,n);R(r,0,n);S(r,0);
if(ans==make_pair(I,I))puts("No solution");
else print(ans.first),putchar(32),print(ans.second),putchar(10);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话