目录
点分治
点分治是树分治中应用最广泛的一种,常用于解决一类求树上满足条件的点对相关的问题,与其它的算法与数据结构结合后能解决多种类型的树上问题,可以说其作用媲美树链剖分。
介绍
树分治的中心思想就是将一个规模很大的问题分解为几个两个相同形式的子问题,然后进行合并。
我们首先面临的问题就是如何将一颗树最优地划分为两块,而本文所涉及的点分治便是划分方法的其中一种;其思想主要是以一个点为基准,然后处理过此点的链或点对,至于其它的不断分治下去处理。
我们称作为分治基准点的这个点为重心,那么我们肯定希望它的子树大小比较平均,也就是说,我们要找到一个最大子树最小的点作为重心,具体做法很简单,注意考虑在深度优先遍历意义下作为点祖先的那些点组成的子树即可。
可以证明:点分治的时间复杂度为 O(log2N) .
int vis[MAXN],rt;
int mx[MAXN],sz[MAXN],min,dis[MAXN];
void calSz(int u,int fa)//计算子树大小
{
sz[u]=1;mx[u]=0;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=fa&&!vis[v])
{
calSz(v,u);
sz[u]+=sz[v];
mx[u]=std::max(mx[u],sz[v]);
}
}
}
void calRt(int r,int u,int fa)//r:表示要找以 r 为根的子树的重心
{
mx[u]=std::max(mx[u],sz[r]-sz[u]);
if(mx[u]<min) min=mx[u],rt=u;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=fa&&!vis[v]) calRt(r,v,u);
}
}
应用
[POJ1741]Tree【男人八题No.5】
给出一颗树,求距离不大于 k 的点对个数
点数
n≤104
对每一个中心求连线过这个点对距离,排序,随手求一下,加到答案里。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
const int MAXN=1e4+5,MAXM=MAXN<<1;
struct E{int next,to,val;}e[MAXM];int G[MAXN],ecnt;
void addEdge(int u,int v,int w){e[++ecnt]=(E){G[u],v,w};G[u]=ecnt;}
int n,k,vis[MAXN],ans,rt,num;
int mx[MAXN],sz[MAXN],min,dis[MAXN];
void calSz(int u,int fa)
{
sz[u]=1;mx[u]=0;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=fa&&!vis[v])
{
calSz(v,u);
sz[u]+=sz[v];
mx[u]=std::max(mx[u],sz[v]);
}
}
}
void calRt(int r,int u,int fa)
{
mx[u]=std::max(mx[u],sz[r]-sz[u]);
if(mx[u]<min) min=mx[u],rt=u;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=fa&&!vis[v])calRt(r,v,u);
}
}
void calDis(int u,int d,int fa)
{
dis[++num]=d;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=fa&&!vis[v]) calDis(v,d+e[i].val,u);
}
}
int calc(int u,int d)
{
int res=0;num=0;
calDis(u,d,0);
std::sort(dis+1,dis+num+1);
int i=1,j=num;
while(i<j)
{
while(dis[i]+dis[j]>k&&i<j)j--;
res+=j-i;i++;
}
return res;
}
void dc(int u)
{
min=n;calSz(u,0);calRt(u,u,0);
ans+=calc(rt,0);vis[rt]=true;
for(int i=G[rt];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v])
{
ans-=calc(v,e[i].val);
dc(v);
}
}
}
int main()
{
while(scanf("%d%d",&n,&k)&&n&&k)
{
memset(vis,0,sizeof(vis));
memset(G,0,sizeof(G));
ecnt=ans=0;
int u,v,w;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
addEdge(v,u,w);
}
dc(1);
printf("%d\n",ans);
}
return 0;
}
[BZOJ2152]聪聪可可
给出一颗树,用分数表示出树上所有点对的距离和等于是 3 的倍数的概率。
n≤2×104
难点在分数……
/**************************************************************
Problem: 2125
User: zhangche0526
Language: C++
Result: Accepted
Time:432 ms
Memory:21816 kb
****************************************************************/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<stack>
#include<queue>
const int MAXN=1e4+5;
int N,M,Q;
int abs(int x){return (x<0)?(-x):x;}
struct E{int next,to,val;} e[MAXN<<3];int ecnt,G[MAXN];
void addEdge(int u,int v,int w){e[++ecnt]=(E){G[u],v,w};G[u]=ecnt;}
void addEdge2(int u,int v,int w){addEdge(u,v,w);addEdge(v,u,w);}
void initCFS(){memset(G,0,sizeof(G));ecnt=0;}
struct A{int u,v,w;A(int u=0,int v=0,int w=0):u(u),v(v),w(w){};};
int dis[MAXN];bool inQ[MAXN];
std::queue<int> que;
void SPFA(int S)
{
int i;
memset(dis,0x3f,sizeof(dis));
que.push(S);dis[S]=0;inQ[S]=true;
while(!que.empty())
{
int u=que.front();que.pop();
inQ[u]=false;
for(i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].val)
{
dis[v]=dis[u]+e[i].val;
if(!inQ[v]) que.push(v),inQ[v]=true;
}
}
}
}
std::stack<A> st;
int ringLen[MAXN],rcnt;
int belong[MAXN],ringRtDis[MAXN];
int anc[MAXN][20];
void addRing(int u,int v)
{
rcnt++;
while(st.top().u!=u&&st.top().v!=v)
{
A a=st.top();st.pop();
ringRtDis[a.u]=ringRtDis[a.v]+a.w;
ringLen[rcnt]+=a.w;
if(a.u!=u) belong[a.u]=rcnt,anc[a.u][0]=u;
if(a.v!=u) belong[a.v]=rcnt,anc[a.v][0]=u;
}
A a=st.top();st.pop();
ringRtDis[a.u]=ringRtDis[a.v]+a.w;
ringLen[rcnt]+=a.w;
anc[a.v][0]=a.u;
}
int dfn[MAXN],low[MAXN],dcnt;
void tarjan(int u,int la)
{
dfn[u]=low[u]=++dcnt;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==la) continue;
if(!dfn[v])
{
st.push(A(u,v,e[i].val));
tarjan(v,u);
low[u]=std::min(low[u],low[v]);
if(low[v]>=dfn[u])
addRing(u,v);
}else if(dfn[v]<low[u]) low[u]=dfn[v],st.push(A(u,v,e[i].val));
}
}
int dpt[MAXN];
int rebuild(int u,int la)
{
dpt[u]=dpt[la]+1;
for(int i=G[u];i;i=e[i].next)
rebuild(e[i].to,u);
}
inline void initLCA()
{
for(int i=1;(1<<i)<=N;i++)
for(int j=1;j<=N;j++)
anc[j][i]=anc[anc[j][i-1]][i-1];
}
int calDis(int x,int y)
{
int i;
if(dpt[x]<dpt[y]) std::swap(x,y);
int xDis=dis[x],yDis=dis[y];
int maxlogn=std::floor(std::log(N)/std::log(2));
for(i=maxlogn;i>=0;i--)
if(dpt[x]-(1<<i)>=dpt[y])
x=anc[x][i];
if(x==y) return xDis-dis[x];
for(i=maxlogn;i>=0;i--)
if(anc[x][i]!=anc[y][i])
x=anc[x][i],y=anc[y][i];
if(belong[x]&&belong[x]==belong[y])
{
int xyDis=abs(ringRtDis[x]-ringRtDis[y]);
int minDis=std::min(xyDis,ringLen[belong[x]]-xyDis);
return xDis+yDis-dis[x]-dis[y]+minDis;
}else return xDis+yDis-2*dis[anc[x][0]];
}
int main()
{
int i;
scanf("%d%d%d",&N,&M,&Q);
for(i=1;i<=M;i++)
{
int u,v,w;scanf("%d%d%d",&u,&v,&w);
addEdge2(u,v,w);
}
SPFA(1);
tarjan(1,0);
initLCA();
initCFS();
for(i=2;i<=N;i++)
addEdge(anc[i][0],i,0);
rebuild(1,0);
while(Q--)
{
int x,y;scanf("%d%d",&x,&y);
printf("%d\n",calDis(x,y));
}
return 0;
}
动态点分治
[BZOJ1095][ZJOI2007]Hide 捉迷藏
有一棵树,初始时每个点都染成黑色,有两种操作:
- 将一个点的颜色取反;
- 查询距离最远的两个黑点的距离。
点数 N≤105 ,操作数 Q≤5×105 .
我们发现:这道题如果没有更改操作的话,就是一道裸的点分治题目。有了更改操作以后,显然我们每一次更改之后都跑一次点分治是不现实的,那么为了充分利用信息,我们考虑将点分治过程中的重心建成一棵重心树(可以证明:中心树的节点数等于原树,且深度不大于 log2n ),然后对于每一个重心开两个堆 B,C ,以动态维护信息。
操作一个重心时,我们将子树上每一个点父重心的距离压进这个重心的
C
堆中,然后将这一中心
好了,现在只剩下一个问题了:传统的堆是不支持删除操作的,我们需要使用一种可删堆。具体做法是:开两个堆,
因为这道题荒废了半天
/**************************************************************
Problem: 1095
User: zhangche0526
Language: C++
Result: Accepted
Time:18448 ms
Memory:135668 kb
****************************************************************/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
const int MAXN=1e5+5;
int N,Q;
int fa[MAXN];
bool isOff[MAXN];int offNum;
struct E{int next,to;} e[MAXN<<1];int ecnt,G[MAXN];
void addEdge(int u,int v){e[++ecnt]=(E){G[u],v};G[u]=ecnt;}
void addEdge2(int u,int v){addEdge(u,v),addEdge(v,u);}
class HEAP
{
private:
std::priority_queue<int> extH,delH;
void clean()
{
while(!delH.empty()&&extH.top()==delH.top())
{
extH.pop();
delH.pop();
}
}
public:
void push(int x){extH.push(x);}
void del(int x){delH.push(x);}
void pop(){clean();extH.pop();}
int top(){clean();return extH.top();}
int scd()
{
int t1=top();pop();
int t2=top();push(t1);
return t2;
}
int size(){return extH.size()-delH.size();}
} A,B[MAXN],C[MAXN];
class LCA
{
int anc[MAXN][20],dpt[MAXN];
void dfs(int u,int la)
{
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==la) continue;
dpt[v]=dpt[u]+1;
anc[v][0]=u;
dfs(v,u);
}
}
public:
void init()
{
dfs(1,0);
for(int i=1;(1<<i)<=N;i++)
for(int j=1;j<=N;j++)
anc[j][i]=anc[anc[j][i-1]][i-1];
}
int calLCA(int x, int y)
{
int i;
if(dpt[x]<dpt[y]) std::swap(x,y);
int maxlogn=std::floor(std::log(N)/std::log(2));
for(i=maxlogn;i>=0;i--)
if(dpt[x]-(1<<i)>=dpt[y])
x=anc[x][i];
if(x==y) return x;
for(i=maxlogn;i>=0;i--)
{
if(anc[x][i]!=anc[y][i])
{
x=anc[x][i];
y=anc[y][i];
}
}
return anc[x][0];
}
int calDis(int x,int y)
{return dpt[x]+dpt[y]-2*dpt[calLCA(x,y)];}
} lca;
class PDC
{
private:
int vis[MAXN],rt;
int mx[MAXN],sz[MAXN],min;
void calSz(int u,int la)
{
sz[u]=1;mx[u]=0;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=la&&!vis[v])
{
calSz(v,u);
sz[u]+=sz[v];
mx[u]=std::max(mx[u],sz[v]);
}
}
}
void calRt(int r,int u,int la)
{
mx[u]=std::max(mx[u],sz[r]-sz[u]);
if(mx[u]<min) min=mx[u],rt=u;
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=la&&!vis[v])calRt(r,v,u);
}
}
void addC(int u,int la)
{
C[rt].push(lca.calDis(u,fa[rt]));
for(int i=G[u];i;i=e[i].next)
{
int v=e[i].to;
if(v!=la&&!vis[v])
addC(v,u);
}
}
public:
void build(int u,int laRt)
{
min=N;
calSz(u,0);
calRt(u,u,0);
vis[rt]=true;fa[rt]=laRt;
if(laRt) addC(rt,0);
if(C[rt].size()) B[laRt].push(C[rt].top());
int oRt=rt;
for(int i=G[rt];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v]) build(v,oRt);
}
}
} ptDC;
void turnOn(int x)
{
for(int i=x;fa[i];i=fa[i])
{
if(B[fa[i]].size()>=2) A.del(B[fa[i]].top()+B[fa[i]].scd());
if(C[i].size()) B[fa[i]].del(C[i].top());
C[i].del(lca.calDis(fa[i],x));
if(C[i].size()) B[fa[i]].push(C[i].top());
if(B[fa[i]].size()>=2) A.push(B[fa[i]].top()+B[fa[i]].scd());
}
}
void turnOff(int x)
{
for(int i=x;fa[i];i=fa[i])
{
if(B[fa[i]].size()>=2) A.del(B[fa[i]].top()+B[fa[i]].scd());
if(C[i].size()) B[fa[i]].del(C[i].top());
C[i].push(lca.calDis(fa[i],x));
if(C[i].size()) B[fa[i]].push(C[i].top());
if(B[fa[i]].size()>=2) A.push(B[fa[i]].top()+B[fa[i]].scd());
}
}
inline int read()
{
int x=0,flag=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')flag=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*flag;
}
int main()
{
int i;
offNum=N=read();
for(i=1;i<=N;i++) isOff[i]=true;
for(i=1;i<N;i++)
{
int u,v;u=read();v=read();
addEdge2(u,v);
}
lca.init();
ptDC.build(1,0);
for(i=1;i<=N;i++)
if(B[i].size()>=2)
{
A.push(B[i].top()+B[i].scd());
}
Q=read();
char opt[10];
while(Q--)
{
scanf("%s",opt);
if(opt[0]=='G')
{
if(offNum==0) printf("-1\n");
else if(offNum==1) printf("-1\n");
else printf("%d\n",A.top());
}
else
{
int x=read();
if(isOff[x])
{
isOff[x]=false;
offNum--;
turnOn(x);
}else
{
isOff[x]=true;
offNum++;
turnOff(x);
}
}
}
return 0;
}