洛谷P4383 [八省聯考2018]林克卡特樹lct(DP凸優化/wqs二分)


題目描述

小L 最近沉迷於塞爾達傳說:荒野之息(The Legend of Zelda: Breath of The Wild)無法自拔,他尤其喜歡游戲中的迷你挑戰。

游戲中有一個叫做“LCT” 的挑戰,它的規則是這樣子的:現在有一個N 個點的 樹(Tree),每條邊有一個整數邊權vi ,若vi >= 0,表示走這條邊會獲得vi 的收益;若vi < 0 ,則表示走這條邊需要支付- vi 的過路費。小L 需要控制主角Link 切掉(Cut)樹上的 恰好K 條邊,然后再連接 K 條邊權為 0 的邊,得到一棵新的樹。接着,他會選擇樹上的兩個點p; q ,並沿着樹上連接這兩點的簡單路徑從p 走到q ,並為經過的每條邊支付過路費/ 獲取相應收益。

海拉魯大陸之神TemporaryDO 想考驗一下Link。他告訴Link,如果Link 能切掉 合適的邊、選擇合適的路徑從而使 總收益 - 總過路費最大化的話,就把傳說中的大師之劍送給他。

小 L 想得到大師之劍,於是他找到了你來幫忙,請你告訴他,Link 能得到的 總收益 - 總過路費最大是多少。

輸入輸出格式

輸入格式:

從文件lct.in 中讀入數據。

輸入第一行包含兩個正整數N; K,保證0 <= K < N <= 3* 10^5105 。

接下來N - 1 行,每行包含三個整數xi; yi; vi,表示第i 條邊連接圖中的xi; yi 兩點, 它的邊權為vi。

輸出格式

輸出到文件lct.out 中。

輸出一行一個整數,表示答案。

輸入輸出樣例

輸入樣例#1:  復制
5 1
1 2 3
2 3 5
2 4 -3
4 5 6
輸出樣例#1:  復制
14

說明

【樣例1 解釋】

一種可能的最優方案為:切掉(2; 4; ?3) 這條邊,連接(3; 4; 0) 這條邊,選擇(p; q) = (1; 5)。

• 對於10% 的數據,k = 0 ;

• 對於另外10% 的數據,k = 1 ;

• 對於另外15% 的數據,k = 2 ;

• 對於另外25% 的數據,k <= 100 ;

• 對於其他數據,沒有特殊約定。

對於全部的測試數據,保證有1 <= N <= 3 * 10^5105 ; 1 <= xi; yi <= N; |vi| <= 10^6106 。

【提示】

題目並不難。

 

當時在考場上,,只會$k=0$還掛了一個點。。

題目等價於:在樹上選擇$k+1$條不相交的鏈,使其權值和最大。

考慮樹形DP(以下的$k$均為$k+1$)

一個很直觀的想法是用$f[i][j]$表示第$i$個節點,子樹中選了$j$條鏈的最大價值。

但這樣是無法轉移的,因此我們要考慮到根節點的情況,

令$f[0/1/2][i][j]$表示$i$號節點的子樹中選了$j$條鏈,根節點不在任何一條鏈中/作為鏈的端點/作為兩條鏈的端點的最大值。

更新的時候分三種情況討論。

但是這樣復雜度是$O(N*K)$的,考慮繼續優化。

看到這種帶$k$限制類的問題,我們嘗試wqs二分

按照套路,我們觀察$f$數組在不同的$k$下的最優解,不難發現函數是上凸的。嚴格證明不會,自己意會一下吧。。

按照套路,二分一個邊權,加到每條邊上,我們可以通過控制邊權來控制$k$的大小。如果邊權都很小,肯定是少選幾條鏈比較優,如果邊權比較大,肯定是多選幾條優。

按照套路,如果我們二分到一個邊權,在這里恰好選了$k$條鏈,那么這種選法就是最優的。

判斷的時候不需要枚舉$k$,因此DP一遍的復雜度為$O(N)$,總復雜度為$O(NlogV)$

還有就是這玩意兒二分的邊界比較詭異。。。

 

 

// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<vector>
#define Pair pair<int, int>
#define int long long 
const int MAXN = 3 * 1e5 + 1;
using namespace std;
inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
struct Node {
    int x, y;//x :權值   y:數目 
    bool operator < (const Node &rhs) const {
        return this -> x == rhs.x ? this -> y > rhs.y : this -> x < rhs.x;//tag
    }
    Node operator + (const Node &rhs) const {
        return (Node) {x + rhs.x, y + rhs.y};
    }
    Node operator + (const int &rhs) const {
        return (Node) {x + rhs, y};
    }
}f[3][MAXN];
int N, K;
struct Edge {
    int u, v, w, nxt;
}E[MAXN << 1];
int head[MAXN], num = 0;
inline void AddEdge(int x, int y, int z) {
    E[num] = (Edge) {x, y, z, head[x]};
    head[x] = num++;
}
int mid;
Node Plus(Node a) {
    return (Node){a.x - mid, a.y + 1};
}
void dfs(int x, int fa) {
    f[2][x] = max(f[2][x], (Node) {-mid, 1});
    for(int i = head[x]; i != -1; i = E[i].nxt) {
        int v = E[i].v;
        if(v == fa) continue;
        dfs(v, x);
        f[2][x] = max(f[2][x] + f[0][v], Plus(f[1][x] + f[1][v] + E[i].w));//把根與子樹連起來,會增加一條鏈 
        f[1][x] = max(f[1][x] + f[0][v], f[0][x] + f[1][v] + E[i].w);
        f[0][x] = f[0][x] + f[0][v];
        //printf("%d %d %d\n", f[2][x].x, f[1][x].x, f[0][x].x);
    }
    f[0][x] = max(f[0][x], max(Plus(f[1][x]), f[2][x]));
}
main() {
#ifdef WIN32
    freopen("a.in", "r", stdin);
#endif  
    memset(head, -1, sizeof(head));
    N = read(); K = read() + 1;
    int l = 0, r = 0;
    for(int i = 1; i < N; i++) {
        int x = read(), y = read(), z = read();
        AddEdge(x, y, z); AddEdge(y, x, z);
        r += abs(z);
    }
    l = -r;
    while(l <= r) {
        mid = (l + r) >> 1;
        memset(f, 0, sizeof(f));
        dfs(1, 0);
        if(f[0][1].y <= K) r = mid - 1;
        else l = mid + 1;
    }
    mid = l; memset(f, 0, sizeof(f));
    dfs(1, 0);
    printf("%lld", f[0][1].x + mid * K);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM