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的解題思路
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;
}
}