点分治

Posted 殇雪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了点分治相关的知识,希望对你有一定的参考价值。

点分治用来统计树上路径。

点分治的核心是分治。

我们选一个点,统计过该点的路径。然后分治每一颗子树。

我们如果选择重心的话,可以证明,最多递归logn次。

因为选择重心,我们每一次递归的子树节点数都要小于原树的一半,所以log级别。

那么我们如果统计过一点的时间是T(x),则总时间为O(logn*T(n))

以聪聪可可这道题为例:(原题这里

#include<bits/stdc++.h>
#define N 20017
#define eho(x) for(int i=head[x];i;i=net[i])
using namespace std;
int t[8];
int it,x,y,w,n,ans,sum,siz[N],head[N],vis[N],f[N],net[N<<1],fall[N<<1],tot,cost[N<<1];
#define sight(c) (‘0‘<=c&&c<=‘9‘)
inline void read(int &x){
    static char c;
    for (c=getchar();!sight(c);c=getchar());
    for (x=0;sight(c);c=getchar())x=x*10+c-48;
}
int gcd(int x,int y) {
    if (!y) return x;return gcd(y,x%y);
}
inline void add(int x,int y,int w){
    fall[++tot]=y; net[tot]=head[x]; head[x]=tot; cost[tot]=w;
} 
void dfs(int x,int fa){
    siz[x]=1;eho(x) if (fall[i]^fa&&!vis[fall[i]]) dfs(fall[i],x),siz[x]+=siz[fall[i]];
}
void dfs2(int x,int fa){
    f[x]=sum-siz[x];
    eho(x) if (fall[i]^fa&&!vis[fall[i]]) f[x]=max(f[x],siz[fall[i]]),dfs2(fall[i],x);
    if (f[it]>f[x]) it=x;
}
void dfs3(int x,int fa,int to){
    t[to]++;
    eho(x) if (fall[i]^fa&&!vis[fall[i]]) dfs3(fall[i],x,(to+cost[i])%3);
}
int cal(int x,int val) {
    t[1]=t[2]=t[0]=0;
    dfs3(x,0,val%3);
    return t[1]*t[2]*2+t[0]*t[0];
}
void sol(int pos){
    dfs(pos,0);sum=siz[pos];it=0;
    dfs2(pos,0);pos=it;ans+=cal(pos,0);vis[pos]=1;
    eho(pos)if (!vis[fall[i]]){
        ans-=cal(fall[i],cost[i]);
        sol(fall[i]);
    }
}
int main () {
    read(n);
    for (int i=1;i<n;i++) {
        read(x),read(y),read(w);w%=3;
        add(x,y,w); add(y,x,w);}
    f[0]=n;
    sol(1);
    int t=gcd(ans,n*n);
    printf("%d/%d\n",ans/t,n*n/t);
}

再说一个非常迷的事情:

有些std的solve :

void solve(int u)
{
    ans+=calc(u,0);
    vis[u]=1;//将当前点标记
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(vis[v]) continue;
        ans-=calc(v,e[i].w);
        root=0;//初始化根  
        sum=son[v];//初始化sum,注意这里的sum不一定是子树的大小
        getroot(v,0);//找连通块的根
        solve(root);//递归处理下一个连通块
    }
}

那么有一个问题,我们每次分出一个重心后,可能有一颗子树是向上的,但这样写就统计不出向上的那颗子树的正确的大小,但是我们的点分治只是跟时间复杂度有关,与答案无关,所以总是能跑出正确的答案,而多次尝试可以证明,这样用向下的子树大小来取代真实的子树大小,在一般数据下是没有关系的(由于向上的子树每次都期望按一定比例缩小,所以还是保证log级别的复杂度)。但我不知道会不会存在特殊数据使这种写法退化成O(N^2)。(有大佬知道的话请告诉我),但总之这种写法总是让新学者很迷(比如我),所以出来解释一下。

以上是关于点分治的主要内容,如果未能解决你的问题,请参考以下文章

基于点分治的树分治

POJ 1741 Tree ——点分治

bzoj2599 [ IOI2011] -- 点分治

算法有关点分治的一些理解与看法

POJ 3714 分治/求平面最近点对

分治——最近点对