其实点分治也是暴力,用于处理树上一些奇怪的东西.
我们直接来看题.
luogu P3806 [模板] 点分治1
给你一棵树,每一条边都有边权,给出很多询问,询问树上是否存在距离为k的点对.
k≤107
k
≤
10
7
,我们直接处理所有点之间的距离放在一个数组里,
O(1)
O
(
1
)
回答每一个询问.
那怎么搞呢?
对于某个点,我们思考一下,路径只可能有2种情况.
1.路径穿过了该点.
也就是说,该路径是该点的一棵子树里的某个点到另一棵子树里的某个点之间的距离(穿过它自己也行).
2.路径没有穿过该点.
也就是说,该路径是该点的同一棵子树里的两个点之间的距离.
我们在算的时候,先暴力去扫每一个点到根节点的距离,把所有的距离放到一个
vector
v
e
c
t
o
r
里,再爆扫一遍,对于每两个距离都暴力计数到
sum
s
u
m
数组里.
但是稍微思考一下,很明显多算了第二种可能情况的路径.
这时候再暴力跑一遍它的每个子树,把多出来的减掉.
跑的时候注意设初始距离为根节点到它之间那条边的权值,具体在代码里会说.
可是单单是这样还没完.
时间!对于上述的算法来说,选定子树的根节点极其重要.
如果该树是一条链,你每次从根节点跑到叶节点,时间已经飞了.
显然我们需要将子树的大小分摊开来.
这时就要选定接近于中间的点—-重心.
那么对每一棵树都求重心,对每一棵子树都暴力跑一下再容斥,最后
O(1)
O
(
1
)
回答询问,我们成功地……
TLE
T
L
E
.
好吧再开个O2,轻松AC此题.
// luogu-judger-enable-o2
#include<bits/stdc++.h> //Ithea Myse Valgulious
namespace chtholly{
typedef long long ll;
#define re0 register int
#define rec register char
#define rel register ll
#define gc getchar
#define pc putchar
#define p32 pc(' ')
#define pl puts("")
/*By Citrus*/
inline int read(){
int x=0,f=1;char c=gc();
for (;!isdigit(c);c=gc()) f^=c=='-';
for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
return f?x:-x;
}
template <typename mitsuha>
inline bool read(mitsuha &x){
x=0;int f=1;char c=gc();
for (;!isdigit(c)&&~c;c=gc()) f^=c=='-';
if (!~c) return 0;
for (;isdigit(c);c=gc()) x=(x<<3)+(x<<1)+(c^'0');
return x=f?x:-x,1;
}
template <typename mitsuha>
inline int write(mitsuha x){
if (!x) return 0&pc(48);
if (x<0) x=-x,pc('-');
int bit[20],i,p=0;
for (;x;x/=10) bit[++p]=x%10;
for (i=p;i;--i) pc(bit[i]+48);
return 0;
}
inline char fuhao(){
char c=gc();
for (;isspace(c);c=gc());
return c;
}
}using namespace chtholly;
using namespace std;
const int yuzu=1e5,mulu=1e7,inf=0x3f3f3f3f;
struct edge{int to,cost;};
vector<edge> lj[yuzu|10];//存放边的vector
typedef int fuko[yuzu|10];
fuko sz,mxs,vis;
/*size,maxson,vis,depth*/
int n,m,xiao,rt,nsz,sum[mulu|10];
vector<int> dist;
/*n,m,min,root,count of road,nowsize*/
void grt(int u,int fa){//getroot
sz[u]=1,mxs[u]=0;//以u为根节点的子树大小赋值为1,u所在树的最大子树大小为mxs[u].
for (edge i:lj[u]){
int v=i.to,c=i.cost;
if (!vis[v]&&v^fa){//这里还是有vis数组,因为已经处理过的节点不能再处理了.
grt(v,u),sz[u]+=sz[v];
mxs[u]=max(mxs[u],sz[v]);//mxs[u],是最大子树大小.
}
}
mxs[u]=max(mxs[u],nsz-mxs[u]);//该节点子树之外的节点数量,保证该节点平均.nsz是目前要求的子树大小.
if (mxs[u]<xiao) xiao=mxs[u],rt=u;//更新root
}
void getdist(int u,int fa,int now){//在dist数组里加入每个点到根节点的距离.
dist.push_back(now);
for (edge i:lj[u]){
int v=i.to,c=i.cost;
if (!vis[v]&&v^fa) getdist(v,u,now+c);
}
}
void getsum(int u,int dis,int bo){//处理sum数组,bo为1或者-1,表示是加还是减.
int i,j;
dist.clear();
getdist(u,0,dis);
for (i=0;i<dist.size();++i){//暴力在sum数组里处理.
for (j=i+1;j<dist.size();++j){
sum[dist[i]+dist[j]]+=bo;
}
}
}
#define init(x) rt=0,nsz=x,xiao=inf//准备处理下一个子树.
void solve(int u){
vis[u]=1,getsum(u,0,1);//先对u这个点跑一遍.
for (edge i:lj[u]){
int v=i.to,c=i.cost;
if (!vis[v]){
getsum(v,c,-1);//容斥一下.需要注意的是,这里的初始距离不是0,因为刚才算的时候重复的情况是这两个点到u的路径长度都加上c.
init(sz[v]),grt(v,0);
solve(v);//求出v所在子树的重心并处理.(等等!我怎么solve(v)了!重心呢)竟然能过!开了O2快了这么多!
}
}
}
int main(){
int i,j;n=read(),m=read();
for (i=1;i<n;++i){
int u=read(),v=read(),c=read();
lj[u].push_back(edge{v,c});
lj[v].push_back(edge{u,c});
}
init(n),grt(1,0),solve(rt);//处理整棵树.
for (;m--;puts(sum[read()]?"AYE":"NAY"));
}
以前有写过一些点分治,不过现在也都忘得差不多,而且那时候码风也差,只会贺题.
推荐Codeforces上一道经典点分治(我并不会)