点分治和cdq分治
点分治
前置知识:树的重心
void dfs(int u,int fa)
{
siz[u]=1;
int maxx=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v, u);
siz[u]+=siz[v];
maxx=max(maxx, siz[v]);
}
ans=min(ans, max(maxx, n-siz[u]));
}
定义
点分治就是将一棵树不断分治为不同子树,然后递归处理问题。一般点分治适用于 在树上求解对长度有一定限制的路径个数等问题。点分治将路径分为经过根和不经过根两类。在当前以rt的子树内,只需求出经过rt的路径数,剩下的递归到它的子树求解。

可能判judge那个地方有点抽象,但是点分治除了分治操作以外,都是纯纯的暴力。当时看bobo课件真的震惊到我了,代码真的不难写。
注意在清空一些数组的时候不能直接用 memset,而应将之前占用过的位置加入一个队列中,进行清空,这样才能保证时间复杂度。
点分治过程中,每一层的所有递归过程合计对每个点处理一次,假设共递归 h 层,则总时间复杂度为 O(hn)。
若我们 每次选择子树的重心作为根节点,可以保证递归层数最少(感觉这个有点像splay操作),时间复杂度为 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e4+5, inf=1e7+5;
int n, m, ask[maxn], del[maxn], d[maxn], siz[maxn], cnt, dis[maxn], sum, mxs, root;
int head[maxn], edgenum, q[inf], judge[inf], ans[maxn];
struct edge{
int next;
int to;
int w;
}edge[maxn<<1];
void add(int from,int to,int w)
{
edge[++edgenum].next=head[from];
edge[edgenum].to=to;
edge[edgenum].w=w;
head[from]=edgenum;
}
void getrt(int u,int fa)
{
siz[u]=1;
int maxx=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
getrt(v, u);
siz[u]+=siz[v];
maxx=max(maxx, siz[v]);
}
maxx=max(maxx, sum-siz[u]);
if(maxx<mxs)
{
mxs=maxx, root=u;
}
}
void getdis(int u,int fa)
{
dis[++cnt]=d[u];
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;//注意!
d[v]=d[u]+edge[i].w;
getdis(v, u);
}
}
void calc(int u)
{
del[u]=judge[0]=1;//注意!
int p=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
cnt=0;
d[v]=edge[i].w;
getdis(v, u);
for(int j=1;j<=cnt;j++)
for(int k=1;k<=m;k++)
if(ask[k]>=dis[j])
ans[k]|=judge[ask[k]-dis[j]];
for(int j=1;j<=cnt;j++)
if(dis[j]<inf)
q[++p]=dis[j], judge[q[p]]=1;
}
for(int i=1;i<=p;i++) judge[q[i]]=0;//注意!点分治的每篇代码这里都要清空
}
void divide(int u)
{
calc(u);
del[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
mxs=sum=siz[v];
getrt(v, 0);
divide(root);//注意!这里是root不是v
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<n;i++)
{
int u, v, w;
cin>>u>>v>>w;
add(u, v, w), add(v, u, w);
}
for(int i=1;i<=m;i++)
{
cin>>ask[i];
}
mxs=sum=n;
getrt(1, 0);
getrt(root, 0);//注意!要跑两遍
divide(root);
for(int i=1;i<=m;i++)
{
if(ans[i]) cout<<"AYE"<<endl;
else cout<<"NAY"<<endl;
}
return 0;
}
练习
练1:[bzoj1468]Tree
这里放的不是洛谷上的链接因为这个题和洛谷上的那个不一样qwq。洛谷数据还是太水了点。。
洛谷上直接枚举判断就可以水过去了,但复杂度较高,考虑用树状数组优化,用树状数组维护每个值的个数,修改、查询比较基础。
练2:聪聪可可
比较套路的题,算出两点间路径和为3的倍数的路径数量ans,那么他赢的概率即为 \((ans*2+n)/(n*n)\)。
练3:[bzoj2599][IOI2011]Race
看起来还是比较套路地写法,但是比较值得注意的就是q数组的变化,可以像例题里的judge数组一样记录权值为()的路径的变数最小值,答案存储时不断更新。
像这样子:
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
cnt=0;
d[v]=edge[i].w;
getdis(v, u, 1);
for(int j=1;j<=cnt;j++)
if(K>=dis[j])
ans=min(ans, dep[j]+q[K-dis[j]]);
for(int j=1;j<=cnt;j++)
if(dis[j]<=K)
q[dis[j]]=min(dep[j], q[dis[j]]);
}
记得清空哦。
练4:采药人的路径
这个题还是蛮有意思的。设阴的边权为-1,阳的为1,那么满足阴阳平衡的即为路径和为0的。这个就可以判断整条路是否符合要求。现在考虑这个休息站,记每个点到根的距离为dis[i],加入从这个点到根的路径上有另一个节点满足dis[j]=dis[i],就给i这一节点打上标记,分为以下4种情况:

感觉这个标记真的挺巧妙的,主要就是要观察这个休息站的特点(即连接的两点dis值相同),发现这一性质后就能好想一点了。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5, inf=0x3f3f3f3f;
int n, K, ask[maxn], del[maxn], d[maxn], siz[maxn], cnt, sum, mxs, root, ans;
int head[maxn], edgenum, q[maxn], t1, t2, t[maxn][2], tot, dp[maxn][2], vis[maxn];
struct edge{
int next;
int to;
int w;
}edge[maxn<<1];
inline void add(int from,int to,int w)
{
edge[++edgenum].next=head[from];
edge[edgenum].to=to;
edge[edgenum].w=w;
head[from]=edgenum;
}
inline void getrt(int u,int fa)
{
siz[u]=1;
int maxx=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
getrt(v, u);
siz[u]+=siz[v];
maxx=max(maxx, siz[v]);
}
maxx=max(maxx, sum-siz[u]);
if(maxx<mxs)
{
mxs=maxx, root=u;
}
}
inline void getdis(int u,int fa)
{
if(vis[d[u]+n]) t[++t2][1]=d[u]+n;
else t[++t1][0]=d[u]+n;
vis[d[u]+n]++;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
d[v]=d[u]+edge[i].w;
getdis(v, u);
}
vis[d[u]+n]--;
}
inline void calc(int u)
{
del[u]=1;
int p=0;
q[0]=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
cnt=t1=t2=0;
d[v]=edge[i].w;
getdis(v, u);
for(int j=1;j<=t1;j++)
{
int tmp=t[j][0];
if(tmp==n) ans+=dp[tmp][0]+dp[tmp][1];
else ans+=dp[2*n-tmp][1];
}
for(int j=1;j<=t2;j++)
{
int tmp=t[j][1];
if(tmp==n) ans+=dp[tmp][0]+dp[tmp][1]+1;
else ans+=dp[2*n-tmp][0]+dp[2*n-tmp][1];
}
for(int j=1;j<=t1;j++)
dp[t[j][0]][0]++, q[++p]=t[j][0];
for(int j=1;j<=t2;j++)
dp[t[j][1]][1]++, q[++p]=t[j][1];
}
for(int k=1;k<=p;k++) dp[q[k]][0]=dp[q[k]][1]=0;
}
inline void divide(int u)
{
calc(u);
del[u]=1;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
mxs=sum=siz[v];
getrt(v, 0);
divide(root);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin>>n;
for(int i=1;i<n;i++)
{
int u, v, w;
cin>>u>>v>>w;
if(w==0) w=-1;
add(u, v, w), add(v, u, w);
}
mxs=sum=n;
getrt(1, 0);
getrt(root, 0);
divide(root);
cout<<ans;
return 0;
}
练5:「BJOI2017」树的难题
当时想到了一些合并的思路什么的,显然从一个根分治的话,最棘手的问题就是这个根和它子节点所连的第一条边的颜色。然后
动态点分治/点分树
在做了极大的思想斗争后决定一定要写博客证明自己学会了。
动态,即带修,和淀粉质的区别就是①有修改②多次查询。因为在淀粉质中我们是先求重心然后处理问题,那么基于这种思路我们将原树的重心作为新的rt,对于rt的每一个直接相连的子节点 构成的子树中的 重心与rt相连,然后这样递归下去重构出一棵树,这棵树就叫做点分树。
点分树还是和点分治一样有一定暴力性质的,就比如暴力跳点分树修改。在点分树上的每个节点维护一些值(一般有两个:当前节点(u)答案、其父节点中u子树内的答案),查询时暴力跳点分树,根据维护的值计算答案。然而一般的题因为题目中的一些限制,需要用线段树、前缀和等优化,所以码量还是很大的!
点分树性质:
- 点分树最大深度是log级别的。
- 点分树上两点的lca在原树上两点之间的路径上。
例:[BZOJ3730]点分树 | 震波(模板)
注意到题目中的 距离不超过k 这一限制,再注意到k的范围居然是1e5,我们想到了线段树。

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+5, inf=1e18;
int n, m, a[maxn];
int head[maxn], edgenum;
struct edge{
int next;
int to;
int w;
}edge[maxn<<1];
inline void add(int from,int to,int w)
{
edge[++edgenum].next=head[from];
edge[edgenum].to=to;
edge[edgenum].w=w;
head[from]=edgenum;
}
struct LCA{
int dep[maxn], f[maxn][20];
void dfs(int u,int fa)
{
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=19;i++)
{
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v, u);
}
}
int lca(int u,int v)
{
if(dep[u]<dep[v]) swap(u, v);
for(int i=19;i>=0;i--)
{
if(dep[f[u][i]]>=dep[v]) u=f[u][i];
}
if(u==v) return v;
for(int i=19;i>=0;i--)
{
if(f[u][i]!=f[v][i])
{
u=f[u][i], v=f[v][i];
}
}
return f[u][0];
}
int getdis(int x,int y)
{
return dep[x]+dep[y]-2*dep[lca(x, y)];
}
}lca;
struct tree{
int idx, root[maxn];
struct seg_tree{
int l, r, sum;
}tr[maxn*40];
void pushup(int id)
{
tr[id].sum=tr[tr[id].l].sum+tr[tr[id].r].sum;
}
void update(int &id,int l,int r,int d,int v)
{
if(!id) id=++idx;
if(l==r)
{
tr[id].sum+=v;
return ;
}
int mid=(l+r)>>1;
if(d<=mid) update(tr[id].l, l, mid, d, v);
else update(tr[id].r, mid+1, r, d, v);
pushup(id);
}
int query(int id,int l,int r,int x,int y)
{
if(!id||r<x||l>y) return 0;
if(x<=l&&r<=y) return tr[id].sum;
int mid=(l+r)>>1;
return query(tr[id].l, l, mid, x, y)+query(tr[id].r, mid+1, r, x, y);
}
}sg, ch;
struct pointree{
int root, sum, siz[maxn], maxx[maxn], del[maxn];
void getrt(int u,int fa)
{
siz[u]=1;
maxx[u]=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
getrt(v, u);
siz[u]+=siz[v];
maxx[u]=max(maxx[u], siz[v]);
}
maxx[u]=max(maxx[u], sum-siz[u]);
if(maxx[u]<maxx[root]) root=u;
}
void getroot(int u,int size)
{
maxx[root]=sum=size;
getrt(u, 0);
getrt(root, 0);
}
int dis[maxn][20], dep[maxn], f[maxn];
void build_sg(int u,int fa,int rt,int d)
{
sg.update(sg.root[rt], 0, n, d, a[u]);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
build_sg(v, u, rt, d+1);
}
}
void build_ch(int u,int fa,int rt,int d)
{
ch.update(ch.root[rt], 0, n, d, a[u]);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa||del[v]) continue;
build_ch(v, u, rt, d+1);
}
}
void build(int u)
{
del[u]=1;
build_sg(u, 0, u, 0);
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(del[v]) continue;
getroot(v, siz[v]);
build_ch(v, 0, root, 1);
f[root]=u;
dep[root]=dep[u]+1;
build(root);
}
}
void init()
{
getroot(1, n);
build(root);
lca.dfs(1, 0);
for(int i=1;i<=n;i++)
{
for(int j=i;j;j=f[j])
{
dis[i][dep[i]-dep[j]]=lca.getdis(i, j);
}
}
}
int query(int x,int y)
{
int res=sg.query(sg.root[x], 0, n, 0, y);
for(int i=x;f[i];i=f[i])
{
int d=dis[x][dep[x]-dep[f[i]]];
res+=sg.query(sg.root[f[i]], 0, n, 0, y-d);
res-=ch.query(ch.root[i], 0, n, 0, y-d);
}
return res;
}
void update(int x,int y)
{
sg.update(sg.root[x], 0, n, 0, y-a[x]);
for(int i=x;f[i];i=f[i])
{
int d=dis[x][dep[x]-dep[f[i]]];
sg.update(sg.root[f[i]], 0, n, d, y-a[x]);
ch.update(ch.root[i], 0, n, d, y-a[x]);
}
}
}t;
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<n;i++)
{
int u, v;
cin>>u>>v;
add(u, v, 1), add(v, u, 1);
}
t.init();
int lst=0;
while(m--)
{
int opt, x, y;
cin>>opt>>x>>y;
x^=lst, y^=lst;
if(opt==0)
{
lst=t.query(x, y);
cout<<lst<<endl;
}
else t.update(x, y), a[x]=y;
}
return 0;
}
练习记得补上

浙公网安备 33010602011771号