[八省联考2018]林克卡特树

题目描述

小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
原文地址:https://www.cnblogs.com/ZH-comld/p/10380860.html