POJ 1741 Tree 點(diǎn)分樹題解

Openjudge原題鏈接

POJ原題鏈接

  • 題意
    輸入樹的各邊及其長度,求路徑小于等于k的點(diǎn)對數(shù)目

  • 題解
    目標(biāo):尋找長度小于等于k的路徑
    首先選取一個(gè)點(diǎn)作為根節(jié)點(diǎn),那么路徑只有兩種情況:經(jīng)過根節(jié)點(diǎn);不經(jīng)過根節(jié)點(diǎn)
    如果不經(jīng)過根節(jié)點(diǎn),那么一定經(jīng)過最小公共子樹的根節(jié)點(diǎn),劃歸為問題1,可分治求解。

  • 假定我們已經(jīng)有了根節(jié)點(diǎn)和樹的結(jié)構(gòu),那么只需要進(jìn)行一次dfs,在dfs過程中求出每個(gè)節(jié)點(diǎn)到根節(jié)點(diǎn)的距離deep,以及計(jì)算出deep[x] + deep[y] <= k的pair數(shù)目,然后刪除根節(jié)點(diǎn),然后進(jìn)入每一個(gè)子樹重新計(jì)算新的deep并計(jì)算數(shù)目。但這樣會重復(fù)計(jì)算。所以在第一次計(jì)算時(shí)應(yīng)當(dāng)減去屬于同一棵子樹的pair。

  • 但是,如果樹退化成一條鏈,那么時(shí)間復(fù)雜度為O(N ^ 2)。所以需要每次巧妙地選擇根節(jié)點(diǎn)使得最大的子樹最小。即選擇重心, 這樣保證樹的高度不超過O(logN)。

  • 更多解釋參見代碼注釋

  • 總的時(shí)間復(fù)雜度為O(N(logN)^2)

  • 參考了ACMonster的解釋和wwwiskey的解題思路

  • 參考了關(guān)于點(diǎn)分治的理解-chty

  • 參考了分治算法在樹的路徑問題中的應(yīng)用——IOI2009中國國家集訓(xùn)隊(duì)論文

  • AC代碼如下

#if 0
要尋找長度小于等于k的路徑
首先選取一個(gè)點(diǎn)作為根節(jié)點(diǎn),那么路徑只有兩種情況:
1.經(jīng)過根節(jié)點(diǎn)
2.不經(jīng)過根節(jié)點(diǎn)
如果不經(jīng)過根節(jié)點(diǎn),那么一定經(jīng)過最小公共子樹的根節(jié)點(diǎn),劃歸為問題1,可分治求解。

假定我們已經(jīng)有了根節(jié)點(diǎn)和樹的結(jié)構(gòu),那么只需要進(jìn)行一次dfs,在dfs過程中求出每個(gè)節(jié)點(diǎn)到根節(jié)點(diǎn)的距離deep,以及
計(jì)算出deep[x] + deep[y] <= k的pair數(shù)目,然后刪除根節(jié)點(diǎn),然后進(jìn)入每一個(gè)子樹重新計(jì)算新的deep并計(jì)算數(shù)目。但這
樣會重復(fù)計(jì)算。所以在第一次計(jì)算時(shí)應(yīng)當(dāng)減去屬于同一棵子樹的pair。

但是,如果樹退化成一條鏈,那么時(shí)間復(fù)雜度為O(N ^ 2)。所以需要每次巧妙地選擇根節(jié)點(diǎn)使得最大的子樹最小。即選擇
重心, 這樣保證樹的高度不超過O(logN)。


以下是選擇重心的源碼。
void getroot(int x, int fa)//x表示當(dāng)前結(jié)點(diǎn),fa表示x的父結(jié)點(diǎn)
{
    son[x] = 1; F[x] = 0;//F數(shù)組記錄以x為根的最大子樹的大小
    for (int i = Link[x]; i; i = e[i].next)
        if (e[i].y != fa && !vis[e[i].y])//避免陷入死循環(huán)
        {
            getroot(e[i].y, x);//得到子結(jié)點(diǎn)信息
            son[x] += son[e[i].y];//計(jì)算x結(jié)點(diǎn)大小
            F[x] = max(F[x], son[e[i].y]);//更新F數(shù)組
        }
    F[x] = max(F[x], sum - son[x]);//sum表示當(dāng)前樹的大小,因?yàn)橐詘為根的情況還要考慮以x的父親為根的子樹大小。
    if (F[x]<F[root])root = x;//更新當(dāng)前根
}

以下是刪除根節(jié)點(diǎn)后,遞歸處理每一棵子樹(此時(shí)還不是子樹,是聯(lián)通塊,需要找新的根節(jié)點(diǎn))的源碼
int solve(int x)
{
    vis[x] = 1;//將當(dāng)前點(diǎn)標(biāo)記
    for (int i = Link[x]; i; i = e[i].next)
        if (!vis[e[i].y])
        {
            root = 0;//初始化根  
            sum = e[i].y;//初始化sum
            getroot(x, 0);//找連通塊的根
            solve(e[i].y);//遞歸處理下一個(gè)連通塊
        }
}
int main()
{
    build();//建樹
    sum = f[0] = n;//初始化sum和f[0]
    root = 0;//初始化root
    getroot(1, 0);//找根
    solve(root);//點(diǎn)分治
}
#endif



#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cstring>
#define MAXN 10005
using namespace std;
struct Edge {
    int v, l;//v表示連接到的下一個(gè)節(jié)點(diǎn),l表示長度
    Edge(int v_, int l_) :v(v_), l(l_) {};
};
vector<Edge> g[MAXN];//用變長數(shù)組存儲圖
vector<int> dep;    //用來計(jì)算在某圖下,以某個(gè)節(jié)點(diǎn)作為根節(jié)點(diǎn)時(shí)的各子節(jié)點(diǎn)的deep,它不需要知道具體是哪個(gè)子節(jié)點(diǎn)
int dist[MAXN];     //
int n, k;
int vis[MAXN];
int f[MAXN];//存儲在當(dāng)前圖下,以某個(gè)節(jié)點(diǎn)作為根節(jié)點(diǎn)時(shí),得到的最大子樹的大小
int root;   //當(dāng)前找到的根節(jié)點(diǎn)
int ans;    //最后要求的配對數(shù)目
int s[MAXN];//s for size,記錄在當(dāng)前圖下每個(gè)節(jié)點(diǎn)的子樹大小之和,包括自己
int totalNodes;//n-刪除的點(diǎn)的個(gè)數(shù)
void getroot(int now, int fa) {
    int u;
    //s的存在是為了累加,然后得出父節(jié)點(diǎn)等所有祖先構(gòu)成子樹的大小
    s[now] = 1;//自己
    f[now] = 0;//存儲每個(gè)節(jié)點(diǎn)的最大子樹
    for (int i = 0; i < g[now].size(); i++) {//不會陷入死循環(huán)的原因之一:樹上存在葉節(jié)點(diǎn)(只與父節(jié)點(diǎn)相連)
        u = g[now][i].v;//子節(jié)點(diǎn)
        if (u != fa && !vis[u]) {//不是父節(jié)點(diǎn)且未被切除,如果不排除父節(jié)點(diǎn)會陷入死循環(huán)
            getroot(u, now);//遞歸地找到子樹的最大子樹和子樹的大小
            s[now] += s[u]; //加入子樹的大小
            f[now] = max(f[now], s[u]);//找最大子樹
        }
    }
    f[now] = max(f[now], totalNodes - s[now]);//把父節(jié)點(diǎn)等所有祖先當(dāng)作一棵子樹
    if (f[now] < f[root]) root = now;//找最大子樹最小的根節(jié)點(diǎn)
}
void getdep(int now, int fa) {//在某個(gè)圖某個(gè)根節(jié)點(diǎn)下求deep,天然的深度是dist[now]
    int u;
    dep.push_back(dist[now]);
    s[now] = 1;
    for (int i = 0; i < g[now].size(); i++) {
        u = g[now][i].v;
        if (u != fa && !vis[u]) {
            dist[u] = dist[now] + g[now][i].l;
            getdep(u, now);
            s[now] += s[u];
        }
    }
}
int calc(int now, int len) {//在減去屬于同一棵子樹的時(shí)候,len是這個(gè)子樹到原根節(jié)點(diǎn)的距離
                            //我們把這個(gè)距離考慮進(jìn)來,
    dep.clear();
    dist[now] = len;//以dist[now]作為基礎(chǔ)求deep
    getdep(now, 0);//在當(dāng)前圖當(dāng)前根節(jié)點(diǎn)下,計(jì)算每個(gè)子節(jié)點(diǎn)的deep,放入dep數(shù)組
    sort(dep.begin(), dep.end());
    int cnt = 0;//統(tǒng)計(jì)數(shù)目
    int l = 0, r = dep.size() - 1;
    /*
    用l表示左指針,r表示右指針,i從左向右遍歷。
    如果dep[i]+dep[j]≤k,則點(diǎn)對(i,t)(i<t≤j)都符合題意,將r-l加入答案中,并且l++;否則r--。
    */
    while (l < r) {
        if (dep[l] + dep[r] <= k) {
            cnt += r - l;
            l++;
        }
        else r--;
    }
    return cnt;
}
void work(int now) {//以now作為根節(jié)點(diǎn),在當(dāng)前的圖下計(jì)算滿足題意的pair個(gè)數(shù)
    int u;
    ans += calc(now, 0);//以now作為根節(jié)點(diǎn),在當(dāng)前的圖下所有deep[i]+deep[j]<=k的(i,j)的數(shù)目
                        //然后減去屬于同一棵子樹的(i,j),于是得到了經(jīng)過根節(jié)點(diǎn)的所有(i,j)路徑
    vis[now] = true;//剪掉根節(jié)點(diǎn),得到不連通的幾棵子樹
    for (int i = 0; i < g[now].size(); i++) {
        u = g[now][i].v;
        if (!vis[u]) {
            ans -= calc(u, g[now][i].l);//減去屬于同一棵子樹的(i,j)
            f[0] = totalNodes = s[u];//考慮連通的某棵子樹,其總的大小
            root = 0;
            getroot(u, 0);//找到新的根節(jié)點(diǎn)
            work(root);//遞歸地增加不經(jīng)過當(dāng)前根節(jié)點(diǎn),但是經(jīng)過新的根節(jié)點(diǎn)的路徑數(shù)目
        }
    }
}
int main() {
    while (cin >> n >> k) {
        if (!n && !k) break;
        for (int i = 0; i <= n; i++)
            g[i].clear();
        memset(vis, 0, sizeof(vis));
        int u, v, l;
        for (int i = 1; i < n; i++) {//讀入n-1條邊
            cin >> u >> v >> l;
            g[u].push_back(Edge(v, l));
            g[v].push_back(Edge(u, l));
        }
        f[0] = n;
        root = 0;
        totalNodes = n;
        getroot(1, 0);
        ans = 0;
        work(root);
        cout << ans << endl;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,656評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,647評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,951評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,189評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,419評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評論 2 371

推薦閱讀更多精彩內(nèi)容