Tree POJ - 1741【樹分治】【一句話說清思路】

 

由於該博客的兩位做者瞎幾把亂吹(〃 ̄︶ ̄)人( ̄︶ ̄〃)用彼此的智慧總結出了兩條全新的定理(高度複雜度定理、特異根特異樹定理),轉載請務必說明出處。(逃html

 

 

Pass:anuonei,anuonei,我感受大部分人都不須要這些東西吧,當初研究是由於看不懂別人的博客……還有,有些性質在dalao們看起來是至關顯然,但我真的不懂啊,討論到深更半夜的說。定理都非嚴格證實,由於咱們有點困了(逃)。其實都是鬧着玩的,主要是本身開心興奮自豪懂了就好啦,還望你們都以過家家的心態來看這些中二爆棚的話,以鼓勵代批評啦qwq。java

 

深く感謝しております!q(≧▽≦q)算法

 

 

 「樹簡直TM就是爲分治而生的!,萬歲!」數據結構

 

 

 

1)本題思路:(請結合下一個板塊優化

 

一、  樹上全部路徑都有相同形式:某個能夠成爲某子樹根的節點+其子孫們ui

二、  本題的路徑查詢能夠化簡爲只有一個變量,設u爲i、j的父親或祖先,u合法是dis[i]+dis[j]<=k且i、j節點屬於不一樣子樹,u不合法是dis[i]+dis[j]<=k且i、j節點屬於同一子樹,則有:this

u所有=u合法+u不合法spa

u不合法=u子樹合法.net

最後兩層節點所構成的子樹有:所有=合法(沒有不合法的)htm

因此有公式以下:

根合法

=根所有-根不合法

=根所有-子樹合法

=根所有-子樹所有-子樹不合法

=根所有-子樹所有-子子樹合法

=根所有-子樹所有-子子樹所有-……-最後一棵樹所有

 

具體細節全在代碼裏

 

感謝https://blog.csdn.net/bahuia/article/details/53066373

 

 

 

 

2)筆者結合該題對分治算法的理解以下:

 

一句話,這道題(或說分治算法)其實就是dfs樹,惟一的改進之處就是以平衡點遞歸而非dfs序,(由高度複雜度定理咱們知這會帶來更高效率)。又由特異根特異樹定理咱們知道,同一坨樹,定根不一樣,子樹不一樣,因此每找到一個新的平衡點(newroot),以前儲存的其麾下子樹性質再也不正確,因此每次再找平衡點子樹的平衡點前都要再求一遍子樹性質(logN),求平衡點用去複雜度logN*logN。

每層複雜度O(n),高度複雜度定理知總複雜度N*logN*logN。

 

 

 

3)定理說明以及補充

 

1))此題爲該論文例1:(感謝高中生dalao ,從小到大沒受過這麼大委屈இ௰இ)https://wenku.baidu.com/view/e087065f804d2b160b4ec0b5.html

 

2))高度複雜度定理:樹這個數據結構擁有更高效率,用樹優化的方法的複雜度與高度成正比,等於單層複雜度之和*高度,因此徹底樹的效率優化最明顯,這道題執着於求平衡點就是爲了下降高度。

 

咱們以歸併排序來解釋該定理(請不要以爲畫蛇添足不必看,萬一幫助很大呢(´▽`ʃ♡ƪ))

 

前置技能,圖片也來源於此:http://www.javashuo.com/article/p-nkdsmljg-cd.html

 

 

 一句話解釋:

 

總複雜度爲各節點複雜度之和,對於歸併排序來講,各層複雜度之和恆等於N,對於其餘狀況,咱們依然假設此前提成立(我知道這樣不嚴謹,但也許錯得不是很離譜,是依然有實踐意義的吧,求指教),因此總複雜度=N*logN(每層排序複雜度N*高度)

 

數學遞推證實:

 

T(n)=f(n)+2T(n/2)= n+2T(n/2)

2T(n/2)=2(f(n/2)+2T(n/4))=2(n/2+2T(n/4))

 

……

2T(2)=2(f(2)+2T(1)=2(2+2T(1))

以上各式累加得:

T(n)=n+n+……+n(logn個)=nlogn

 

 

3))特異根特異樹定理:對於一棵樹,定根不一樣,子樹不必定相同。

 

 

public class Tree {
    static IO io = new IO();
    static final int maxn = 10100, inf = Integer.MAX_VALUE / 100;
    static int N, K, cnt, ans;

    static class Edge {
        int v, next, cost;

        public Edge(int v, int next, int cost) {
            this.v = v;
            this.next = next;
            this.cost = cost;
        }
    }

    static Edge[] edges = new Edge[maxn * 2];
    static int[] head = new int[maxn];

    public static void main(String[] args) {
        while (true) {
            N = io.nextInt();
            K = io.nextInt();
//            可讀性呼籲(~ ̄(OO) ̄)ブ,|&都是些什麼鬼,難看死了
//            原本就是搞不懂纔會去搜別人的代碼的
            if (N == 0 && K == 0) return;
            cnt = 0;
            Arrays.fill(head, -1);
            for (int i = 0; i < N - 1; i++) {
                int a = io.nextInt(), b = io.nextInt(), c = io.nextInt();
                add(a, b, c);
                add(b, a, c);
            }
            Arrays.fill(vis, false);
//            重要!!:在遞歸方法裏修改的變量:
//            如須要的結果多是中間結果(newroot),或回溯修改(ans),或全程起某種左右(minmaxchild)
//            爲確保萬無一失,都請務必全局化
//            ans即便使用java的Integer傳參數也會致使錯誤結果(至於爲何,求指教qwq
            ans = 0;
            dfs(1);
            io.println(ans);
        }
    }

    //    單層複雜度=logn+logn+nlogn=nlogn,總複雜度=nlogn*logn
    static boolean[] vis = new boolean[maxn];
//      pre防止經過雙向邊往上//      定根newroot後,已經處理過的點會變成newroot的孫子,vis[]防止遍歷跑出子樹
    static void dfs(int u) {
        minmaxchild = inf;
        getsize(u, -1);
        getnewroot(u, u, -1);
        int newr = newroot;
//        加上以newr爲根的樹所有
//        以newroot!!!!!!!!!!!!!!!
//        以newroot!!!!!!!!!!!!!!!
//        以newroot!!!!!!!!!!!!!!!
        ans += call(newr, 0);
        vis[newr] = true;
        for (int i = head[newr]; i != -1; i = edges[i].next)
            if (!vis[edges[i].v]) {
//                等價於減去以u爲根的樹不合法
//                等價於減去以u爲根的子樹合法
//                newroot遞歸會變,我可不但願循環着循環着錯位了
                ans -= call(edges[i].v, edges[i].cost);
                dfs(edges[i].v);
            }
    }

    //    以u爲根,從上到下普通的dfs遍歷樹,
//    size[i]存的是,以u爲根的樹中,以i爲根的子樹大小
//    maxchild[i]存的是,以u爲根的樹中,以i爲根的因此子樹裏最大子樹的大小
    static int[] size = new int[maxn];
    static int[] maxchild = new int[maxn];

    //    複雜度logn*常數=logn
    static int getsize(int u, int pre) {
        size[u] = 1;
        maxchild[u] = 0;
        for (int i = head[u]; i != -1; i = edges[i].next)
            if (!vis[edges[i].v] && edges[i].v != pre) {
                size[u] += getsize(edges[i].v, u);
                maxchild[u] = Math.max(maxchild[u], size[edges[i].v]);
            }
        return size[u];
    }

    //    以u爲根,從上到下普通的dfs遍歷樹,
//    在這個過程當中,順便找出了newroot
//    minmaxchild是一箇中間變量,是用來篩newroot的標準,不保存,但要初始化
//    以防萬一,按照前面約定好的,minmaxchild設爲全局
//    其意義是:(依然以u爲根),u子樹的各個節點分別爲newroot時,
//    每一個newroot的最大子樹大小裏,最小的那個,也是最平衡的那個
    static int minmaxchild, newroot;

    //    複雜度logn*常數=logn
    static void getnewroot(int r, int u, int pre) {
//        若以u爲平衡點newroot,
//        咱們但願其最大子樹越接近總結點數的一半
        if (minmaxchild > Math.max(maxchild[u], size[r] - maxchild[u])) {
            minmaxchild = Math.max(maxchild[u], size[r] - maxchild[u]);
            newroot = u;
        }
        for (int i = head[u]; i != -1; i = edges[i].next)
            if (!vis[edges[i].v] && edges[i].v != pre)
                getnewroot(r, edges[i].v, u);
    }

    //    一次call算出整顆u根樹的所有
//    dis存的是u根樹裏全部節點到根的距離
    static ArrayList<Integer> dis = new ArrayList<Integer>();

    //    複雜度nlogn+n=nlogn
    static int call(int u, int d) {
//        遍歷複雜度n
        dis.clear();
        filldis(u, d, -1);
//        排序複雜度nlogn
        Collections.sort(dis);
        int i = 0, j = dis.size() - 1, ret = 0;
        while (i < j) {
            while (i < j && dis.get(i) + dis.get(j) > K) j--;
            ret += j - i;
            i++;
        }
        return ret;
    }

    //    複雜度n
    static void filldis(int u, int d, int pre) {
//        把add操做放第一行讓我避免了一些細節上的處理
        dis.add(d);
        for (int i = head[u]; i != -1; i = edges[i].next)
            if (!vis[edges[i].v] && edges[i].v != pre)
                filldis(edges[i].v, edges[i].cost + d, u);
    }

    static void add(int a, int b, int c) {
        edges[cnt] = new Edge(b, head[a], c);
        head[a] = cnt++;
    }
相關文章
相關標籤/搜索