• [八省联考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
  • 相关阅读:
    centos7 安装jdk及mysql8
    centos7 挂载数据盘
    windows server r2 之如何设置共享文件夹访问不需要输入用户名和密码
    svn 常见问题记录
    设计模式之代理模式
    ASP.Net在64位环境开发部署常见问题
    oracle 之创建用户,表空间,授权,修改用户密码
    oracle 常用语句
    IIS服务器 远程发布(Web Deploy)配置 VS2010 开发环境 Windows Server 2008服务器系统
    vs2012使用64位IIS EXPRESS调试
  • 原文地址:https://www.cnblogs.com/ZH-comld/p/10380860.html
Copyright © 2020-2023  润新知