1. 程式人生 > >[八省聯考2018]林克卡特樹

[八省聯考2018]林克卡特樹

style 獲取 getchar col include 狀態 現在 tdi 挑戰

題目描述

小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 能得到的 總收益 - 總過路費最大是多少。

題解

先考慮斷掉k條邊又連上k條邊這個問題,因為最後我們只需要選取最終的樹上的一條鏈,所以我們連上這k條邊相當於是把這條鏈分成k+1段。

所以我們需要找到樹上相互無交的嚴格的k+1條鏈(根據題目描述,一個點也可以算做一條鏈)。

為什麽要嚴格k+1段呢?因為這k+1段之間不能有交,所以必然會空出k條沒有被選的邊,正好可以留給我們斷邊用。

好了,題目大概分析要這裏,然後dp鏈的問題有一個經典做法就是背包,因為不能有交,所以每個點只可能有三種狀態012,表示這個點向下的邊中有幾條被選了,dp時討論一下。

技術分享圖片
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 300002
#define M 103
using namespace std;
typedef long long ll;
ll dp[N][M][3];
int tot,head[N],n,k;
inline ll rd(){
    ll x=0;char c=getchar();bool
f=0; while(!isdigit(c)){if(c==-)f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } struct edge{int n,to;ll l;}e[N<<1]; inline void add(int u,int v,ll l){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;} inline ll mx(ll x,ll y,ll z){ if(x>y&&x>z)return x; return y>z?y:z; } void dfs(int u,int fa){ dp[u][0][0]=dp[u][1][1]=0; for(int i=head[u];i;i=e[i].n)if(e[i].to!=fa){ int v=e[i].to; dfs(v,u); for(int j=k+1;j>=0;--j){ dp[u][j][2]=max(dp[u][j][2],dp[u][j][1]+dp[v][1][1]+e[i].l); for(int l=0;l<j;++l){ ll w=mx(dp[v][j-l][0],dp[v][j-l][1],dp[v][j-l][2]); dp[u][j][0]=max(dp[u][j][0],dp[u][l][0]+w); dp[u][j][1]=max(max(dp[u][j][1],dp[u][l][0]+dp[v][j-l][1]+e[i].l),dp[u][l][1]+w);; dp[u][j][2]=max(dp[u][l][2]+w,max(dp[u][j][2],dp[u][l][1]+dp[v][j-l+1][1]+e[i].l)); } } } } int main(){ n=rd();k=rd();int u,v;ll w; for(int i=1;i<=n;++i)for(int j=0;j<=k+1;++j)for(int l=0;l<3;++l)dp[i][j][l]=-1e16; for(int i=1;i<n;++i){ u=rd();v=rd();w=rd(); add(u,v,w);add(v,u,w); } dfs(1,0); cout<<max(dp[1][k+1][2],max(dp[1][k+1][0],dp[1][k+1][1])); return 0; }
View Code

因為有負邊權的存在,隨著k的增加,答案不一定是嚴格單調遞增的。

但是我們可以發現,答案是一個上凸函數 。

解決這種問題一般的操作是帶權二分。

在這道題中,我們二分一個權mid,代表每選擇一條鏈都要付出-mid的代價。

然後按照上面的方法直接按照權值大小貪心的做O(n)的dp。

然後看看最優解選沒選夠k+1條鏈,夠了就加大mid,否則減小mid。

技術分享圖片
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 300002
#define M 103
#define inf 1e18
using namespace std;
typedef long long ll;
int tot,head[N],n,k;
ll sum,mid,ans;
inline ll rd(){
    ll x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c==-)f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x; 
} 
struct edge{int n,to;ll l;}e[N<<1];
inline void add(int u,int v,ll l){e[++tot].n=head[u];e[tot].to=v;head[u]=tot;e[tot].l=l;}
struct node{
    ll val,x;
    bool operator <(const node &b)const{
        return val==b.val?x>b.x:val<b.val;
    }
    node operator +(const node &b)const{
        return node{val+b.val,x+b.x};
    }
    node operator +(const ll &b)const{
        return node{val+b,x};
    }
}dp[N][3];
void dfs(int u,int fa){
    dp[u][2]=node{-mid,1};dp[u][0]=node{0,0};dp[u][1]=node{0,0};
    for(int i=head[u];i;i=e[i].n)if(e[i].to!=fa){
        int v=e[i].to;
        dfs(v,u);
        dp[u][2]=max(dp[u][2]+dp[v][0],dp[u][1]+dp[v][1]+node{e[i].l-mid,1});
        dp[u][1]=max(dp[u][1]+dp[v][0],dp[u][0]+dp[v][1]+e[i].l);
        dp[u][0]=dp[u][0]+dp[v][0];
    }
    dp[u][0]=max(dp[u][0],max(dp[u][1]+node{-mid,1},dp[u][2]));
}
int main(){
    n=rd();k=rd();int u,v;ll w;k++;
    for(int i=1;i<n;++i){
        u=rd();v=rd();w=rd();sum+=abs(w);
        add(u,v,w);add(v,u,w);
    }
    ll l=-sum,r=sum;
    while(l<=r){
        mid=(l+r)>>1;
        dfs(1,0);
        if(dp[1][0].x<=k)ans=mid,r=mid-1;else l=mid+1;
    }
    mid=ans;
    dfs(1,0);
    cout<<dp[1][0].val+ans*k;
    return 0;
}
View Code

[八省聯考2018]林克卡特樹