1. 程式人生 > >uva 12161 Ironman Race in Treeland 點分治

uva 12161 Ironman Race in Treeland 點分治

題目大意:一棵節點數為n的樹,每條邊有一個長度l和一個花費d,求一條路徑,使得路徑的總花費小於給定的m,且總長度最大

還是點分治,將這個無根樹轉為有根樹之後,一條路徑要麼完全在某一子樹下,要麼經過根,子樹的問題可以遞迴解決,
現在看經過根的情況,按序處理每一棵子樹v,通過dfs可以得到該子樹v所有節點到根的路徑長度和花費,然後需要得到子樹v這個集合
每一個點和已處理過子樹的集合u內每一個點兩兩組合以更新答案,觀察到如果集合v中路徑a的花費高於b,但是a的
長度卻小於b的時候,a一定不會在最優解中,因此可以將a刪除,刪除之後按花費排序後的長度也是有序的,因此可以列舉u中每一個
節點,去v中二分花費,然後更新最優長度,這樣每層的複雜度還是nlogn,按重心點分治得到的是logn層,所以總的複雜度是nlog^2n
 

#include <cstdio>
#include <vector>
#include <utility>
#include <algorithm>
#include <cstring>

using namespace std;
struct node {
    int v, d, l;
}; //儲存樹中的節點
const int INF = 0x3f3f3f3f;
const int maxn = 3e4 + 10;
int n, m, t, root, allnode, ans;
int maxson[maxn], sz[maxn], vis[maxn], depth[maxn], cost[maxn];
//maxson[i]: 去除節點i後得到的森林中節點數最多的樹的節點
//sz:儲存子樹的節點數
//depth記錄長度,cost記錄花費
vector<pair<int, int> > left, right; //left為處理過的子樹中點的集合,right為當前處理的子樹的點的集合
vector<node> G[maxn]; //建圖
//找重心
void find_root(int u, int fa) {
    sz[u] = 1;
    maxson[u] = 0;
    for (int i = 0; i < G[u].size(); i++) {
        node x = G[u][i];
        int v = x.v;
        if (v == fa || vis[v]) continue;
        find_root(v, u);
        sz[u] += sz[v];
        maxson[u] = max(maxson[u], sz[v]);
    }
    maxson[u] = max(maxson[u], allnode - sz[u]);
    if (maxson[root] > maxson[u]) root = u;
}
//計運算元樹所有節點的長度和花費
void dfs(int u, int fa) {
    sz[u] = 1;
    right.push_back({cost[u], depth[u]});
    for (int i = 0; i < G[u].size(); i++) {
        node x = G[u][i];
        int v = x.v;
        int d = x.d;
        int l = x.l;
        if (v == fa ||vis[v]) continue;
        depth[v] = depth[u] + l;
        cost[v] = cost[u] + d;
        dfs(v, u);
        sz[u] += sz[v];
    }
}

void calc(int u) {
    left.clear();
    left.push_back({0, 0});
    sz[u] = 1;
    for (int i = 0; i < G[u].size(); i++) {
        node x = G[u][i];
        int v = x.v;
        if (vis[v]) continue;
        right.clear();
        depth[v] = x.l;
        cost[v] = x.d;
        dfs(v, u);
        sz[u] += sz[v];
        sort(right.begin(), right.end());
        //這裡沒有顯示刪除,而是將需要刪除的節點的路徑和長度賦值為和上一個節點
        for (int j = 1; j < right.size(); j++) {
            if (right[j].second <= right[j - 1].second) {
                right[j].second = right[j - 1].second;
                right[j].first = right[j - 1].first;
            }
        }
        for (int j = 0; j < left.size(); j++) {
            if (left[j].first > m) continue;
            int pos = upper_bound(right.begin(), right.end(), pair<int, int>(m - left[j].first, INF)) - right.begin() - 1;
            if (pos < 0) continue;
            if (right[pos].second + left[j].second > ans) ans = right[pos].second + left[j].second;
        }
        for (int j = 0; j < right.size(); j++) {
            left.push_back(right[j]);
        }
    }
}

void solve(int u) {
    left.clear();
    right.clear();
    calc(u);
    vis[u] = 1;
    for (int i = 0; i < G[u].size(); i++) {
        node x = G[u][i];
        int v = x.v;
        if (vis[v]) continue;
        allnode = sz[v];
        root = 0;
        find_root(v, u);
        solve(root);
    }
}

int main() {
    scanf("%d", &t);
    int cas = 0;
    while (t--) {
        cas ++;
        scanf("%d%d", &n, &m);
        left.reserve(n);
        right.reserve(n);
        memset(vis, 0, sizeof(vis));
        maxson[0] = n;
        for (int i = 1; i <= n; i++) G[i].clear();
        int u, v, d, l;
        for (int i = 1; i < n; i++) {
            scanf("%d%d%d%d", &u, &v, &d, &l);
            G[u].push_back({v, d, l});
            G[v].push_back({u, d, l});
        }
        root = ans = 0;
        allnode = n;
        find_root(1, -1);
        solve(root);
        printf("Case %d: %d\n", cas, ans);
    }
    return 0;
}