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

Openjudge原題鏈接

POJ原題鏈接

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

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

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

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

  • 更多解釋參見(jiàn)代碼注釋

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

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

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

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

  • AC代碼如下

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

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

但是绸栅,如果樹(shù)退化成一條鏈级野,那么時(shí)間復(fù)雜度為O(N ^ 2)。所以需要每次巧妙地選擇根節(jié)點(diǎn)使得最大的子樹(shù)最小粹胯。即選擇
重心, 這樣保證樹(shù)的高度不超過(guò)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為根的最大子樹(shù)的大小
    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)前樹(shù)的大小况鸣,因?yàn)橐詘為根的情況還要考慮以x的父親為根的子樹(shù)大小。
    if (F[x]<F[root])root = x;//更新當(dāng)前根
}

以下是刪除根節(jié)點(diǎn)后竹观,遞歸處理每一棵子樹(shù)(此時(shí)還不是子樹(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();//建樹(shù)
    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表示長(zhǎng)度
    Edge(int v_, int l_) :v(v_), l(l_) {};
};
vector<Edge> g[MAXN];//用變長(zhǎng)數(shù)組存儲(chǔ)圖
vector<int> dep;    //用來(lái)計(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];//存儲(chǔ)在當(dāng)前圖下,以某個(gè)節(jié)點(diǎn)作為根節(jié)點(diǎn)時(shí)誊抛,得到的最大子樹(shù)的大小
int root;   //當(dāng)前找到的根節(jié)點(diǎn)
int ans;    //最后要求的配對(duì)數(shù)目
int s[MAXN];//s for size列牺,記錄在當(dāng)前圖下每個(gè)節(jié)點(diǎn)的子樹(shù)大小之和,包括自己
int totalNodes;//n-刪除的點(diǎn)的個(gè)數(shù)
void getroot(int now, int fa) {
    int u;
    //s的存在是為了累加拗窃,然后得出父節(jié)點(diǎn)等所有祖先構(gòu)成子樹(shù)的大小
    s[now] = 1;//自己
    f[now] = 0;//存儲(chǔ)每個(gè)節(jié)點(diǎn)的最大子樹(shù)
    for (int i = 0; i < g[now].size(); i++) {//不會(huì)陷入死循環(huán)的原因之一:樹(shù)上存在葉節(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ì)陷入死循環(huán)
            getroot(u, now);//遞歸地找到子樹(shù)的最大子樹(shù)和子樹(shù)的大小
            s[now] += s[u]; //加入子樹(shù)的大小
            f[now] = max(f[now], s[u]);//找最大子樹(shù)
        }
    }
    f[now] = max(f[now], totalNodes - s[now]);//把父節(jié)點(diǎn)等所有祖先當(dāng)作一棵子樹(shù)
    if (f[now] < f[root]) root = now;//找最大子樹(shù)最小的根節(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ù)的時(shí)候,len是這個(gè)子樹(shù)到原根節(jié)點(diǎn)的距離
                            //我們把這個(gè)距離考慮進(jìn)來(lái)随夸,
    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)對(duì)(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ì)算滿(mǎn)足題意的pair個(gè)數(shù)
    int u;
    ans += calc(now, 0);//以now作為根節(jié)點(diǎn),在當(dāng)前的圖下所有deep[i]+deep[j]<=k的(i,j)的數(shù)目
                        //然后減去屬于同一棵子樹(shù)的(i,j)妨退,于是得到了經(jīng)過(guò)根節(jié)點(diǎn)的所有(i,j)路徑
    vis[now] = true;//剪掉根節(jié)點(diǎn)妇萄,得到不連通的幾棵子樹(shù)
    for (int i = 0; i < g[now].size(); i++) {
        u = g[now][i].v;
        if (!vis[u]) {
            ans -= calc(u, g[now][i].l);//減去屬于同一棵子樹(shù)的(i,j)
            f[0] = totalNodes = s[u];//考慮連通的某棵子樹(shù)蜕企,其總的大小
            root = 0;
            getroot(u, 0);//找到新的根節(jié)點(diǎn)
            work(root);//遞歸地增加不經(jīng)過(guò)當(dāng)前根節(jié)點(diǎn),但是經(jīng)過(guò)新的根節(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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冠句,一起剝皮案震驚了整個(gè)濱河市轻掩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懦底,老刑警劉巖唇牧,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異聚唐,居然都是意外死亡丐重,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)杆查,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扮惦,“玉大人,你說(shuō)我怎么就攤上這事亲桦⊙旅郏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵烙肺,是天一觀的道長(zhǎng)纳猪。 經(jīng)常有香客問(wèn)我,道長(zhǎng)桃笙,這世上最難降的妖魔是什么氏堤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮搏明,結(jié)果婚禮上鼠锈,老公的妹妹穿的比我還像新娘。我一直安慰自己星著,他們只是感情好购笆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著虚循,像睡著了一般同欠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上横缔,一...
    開(kāi)封第一講書(shū)人閱讀 51,698評(píng)論 1 305
  • 那天铺遂,我揣著相機(jī)與錄音,去河邊找鬼茎刚。 笑死襟锐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的膛锭。 我是一名探鬼主播粮坞,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蚊荣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了莫杈?” 一聲冷哼從身側(cè)響起互例,我...
    開(kāi)封第一講書(shū)人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姓迅,沒(méi)想到半個(gè)月后敲霍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡丁存,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了柴我。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片解寝。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖艘儒,靈堂內(nèi)的尸體忽然破棺而出聋伦,到底是詐尸還是另有隱情,我是刑警寧澤界睁,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布觉增,位于F島的核電站,受9級(jí)特大地震影響翻斟,放射性物質(zhì)發(fā)生泄漏逾礁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一访惜、第九天 我趴在偏房一處隱蔽的房頂上張望嘹履。 院中可真熱鬧,春花似錦债热、人聲如沸砾嫉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)焕刮。三九已至,卻和暖如春墙杯,著一層夾襖步出監(jiān)牢的瞬間配并,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工霍转, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荐绝,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓避消,卻偏偏與公主長(zhǎng)得像低滩,于是被迫代替她去往敵國(guó)和親召夹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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