• 点分治


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

    点分治的核心是分治。

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

    我们如果选择重心的话,可以证明,最多递归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
    ",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);//递归处理下一个连通块
        }
    }

    那么有一个问题,我们每次分出一个重心后,可能有一颗子树是向上的,但这样写就统计不出向上的那颗子树的正确的大小,但是我们的点分治只是跟时间复杂度有关,与答案无关,所以总是能跑出正确的答案,而多次尝试可以证明,这样用向下的子树大小来取代真实的子树大小,但这样是没有关系的(证明点这里)。但这种写法总是让新学者很迷(比如我),所以出来解释一下。

    感谢Darksun2010 大佬的指点。

  • 相关阅读:
    Java并发编程:线程池的使用
    AlarmManager与PendingIntent
    ConnectivityManager与检查网络连接的使用
    IntentService的使用
    Service(Local Service)简介
    Looper、Hander、HandlerThread
    XML_PULL解析
    android AsyncTask 的使用(转载)
    android 连接网络的简单实例
    xml drawable
  • 原文地址:https://www.cnblogs.com/rrsb/p/8278625.html
Copyright © 2020-2023  润新知