题目描述
小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; }
因为有负边权的存在,随着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; }
原文地址:https://www.cnblogs.com/ZH-comld/p/10380860.html