本题是Rinne Loves Edges的变式,多了一个输出最大的切割长度
二叉苹果树题解:https://blog.nowcoder.net/n/c8a9812aa008472b8b31391e7995e29c
二叉苹果树题解:https://blog.nowcoder.net/n/c8a9812aa008472b8b31391e7995e29c
以下是错误题解,一开始做的时候我是这么做的:🤣🤣
跟二叉苹果树同样的思路,把边权下放到点权,这里不再赘述,跑一遍树的构造,再进行dfs记忆化dp。
边界是叶子结点,dp[叶子]=val[叶子]
其他结点,要么把儿子割掉,要么让儿子这颗子树已经割掉了叶子。
dp[root]=Σ min(val[son],dp[son])
本题注意事项:
1.需要两个dp数组存放,分别表示总长度和最长切割路
2.用总长度跟m进行比较,然后输出的是最长切割路
边界是叶子结点,dp[叶子]=val[叶子]
其他结点,要么把儿子割掉,要么让儿子这颗子树已经割掉了叶子。
dp[root]=Σ min(val[son],dp[son])
本题注意事项:
1.需要两个dp数组存放,分别表示总长度和最长切割路
2.用总长度跟m进行比较,然后输出的是最长切割路
详情请看代码:
//下放边权变为点权,对于每一个子树,要么加上儿子的点权,要么加上儿子为结点的整个子树的dp值
#include <bits/stdc++.h>
using namespace std;
int n,m,s;
const int N=1010;
struct Edge
{
int v,w,next;
}e[N<<1];
int cnt=0;
int head[N];
int val[N];
int dp[N];
int length[N];
bool haveson[N];
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
void pushdown(int root,int pre)
{
for(int i=head[root];i;i=e[i].next)
{
int to=e[i].v;
if(to==pre) continue;
haveson[root]=true;
pushdown(to,root);
val[to]=e[i].w;
}
}
void dfs(int root,int pre)
{
if(!haveson[root]) length[root]=dp[root]=val[root];
for(int i=head[root];i;i=e[i].next)
{
int to=e[i].v;
if(to==pre) continue;
dfs(to,root);
dp[root]=max(dp[root],min(dp[to],val[to]));
length[root]+=min(length[to],val[to]);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
pushdown(1,0);
dfs(1,0);
if(length[1]>m) printf("-1\n");
else printf("%d\n",dp[1]);
}
错误的原因:直接向下贪心取边,dp记录的边和length记录的边有可能是不一样的!!!
那么就会导致这个数据是错误的:
4 10
1 2 10
2 3 5
2 4 6
感谢 空白link 大佬提供的数据
1 2 10
2 3 5
2 4 6
感谢 空白link 大佬提供的数据
我的代码会输出6,而真实的答案是10(因为同时取5和6,会超过m==10)
然而,单源最短路的做法也是不对的
会导致这个数据是错误的:
4 11
1 2 10
2 3 5
2 4 6
同样也是 空白link 大佬提供的数据
1 2 10
2 3 5
2 4 6
同样也是 空白link 大佬提供的数据
这样会导致你的答案是10,而应当是6(此时同时取5和6,不会超过m==10)
那么怎样做才是正确的呢?由于答案是单调的,使用二分答案的方式即可
注意不存在的情况输出-1,就是在二分中,if语句的条件从来没有被满足过,即右端点r的值未被修改。
如何编写二分函数:
(dp数组保存的是切割的路中最长的一条,length数组保存的是切割的路的长度总和)
1.对于每个叶子结点,如果val[root]超过了当前二分的答案ans,那么dp[root]=length[root]=无穷大
意思是,这个子树我不要去切割,那么以后取min的时候,肯定是把这个切割的任务交给了父亲或者他的某个祖先
2.对于非叶子结点,看一下切割子树的话,最长路会不会超过ans,超过的话肯定是不能取的,同理,看看切割自己和孩子
相连的这条边,会不会使最长路超过ans,会的话也是不能取的。
根据这个思路,就可以得到以下的代码(完整代码将在最后给出):
if(val[to]>ans&&dp[to]<=ans) {//只有子树合法
dp[root]=max(dp[root],dp[to]);//用子树的dp值更新
length[root]+=length[to];//加上子树的总长度
}
else if(dp[to]>ans&&val[to]<=ans) {//只有与孩子相邻的边合法
dp[root]=max(dp[root],val[to]);//最长路肯定用孩子更新
length[root]+=val[to];//总长度加上孩子
}
else if(dp[to]<=ans&&val[to]<=ans) {//两者都合法
if(length[to]<val[to]) dp[root]=max(dp[root],dp[to]);
else dp[root]=max(dp[root],val[to]);
length[root]+=min(length[to],val[to]);
//正常的使用两者中较小的累加到最长路,dp数组取哪个都无所谓
//因为我只要看dp数组的值有没有比ans大即可,不需要管具体值
}
else {//两者都不行,赋一个最大值,把任务交给父亲或者其他祖先完成
//如果追溯到根结点,任务依旧不能完成,就会导致length无穷大
//那么二分的时候扩大左端点
dp[root]=2000000;
length[root]=2000000;
} 最后,上二分模板直接把dp往里套,值得注意的是,每次二分清空dp数组和length数组
//下放边权变为点权,对于每一个子树,要么加上儿子的点权,要么加上儿子为结点的整个子树的dp值
#include <bits/stdc++.h>
using namespace std;
int n,m,s;
const int N=1010;
struct Edge
{
int v,w,next;
}e[N<<1];
int cnt=0;
int head[N];
int val[N];
int dp[N];
int length[N];
bool haveson[N];
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
void pushdown(int root,int pre)
{
for(int i=head[root];i;i=e[i].next)
{
int to=e[i].v;
if(to==pre) continue;
haveson[root]=true;
pushdown(to,root);
val[to]=e[i].w;
}
}
void dfs(int root,int pre,int ans)
{
if(!haveson[root])
{
if(val[root]<ans)
length[root]=dp[root]=val[root];
else length[root]=dp[root]=2000000;
}
for(int i=head[root];i;i=e[i].next)
{
int to=e[i].v;
if(to==pre) continue;
dfs(to,root,ans);
if(val[to]>ans&&dp[to]<=ans) {//只有子树合法
dp[root]=max(dp[root],dp[to]);//用子树的dp值更新
length[root]+=length[to];//加上子树的总长度
}
else if(dp[to]>ans&&val[to]<=ans) {//只有与孩子相邻的边合法
dp[root]=max(dp[root],val[to]);//最长路肯定用孩子更新
length[root]+=val[to];//总长度加上孩子
}
else if(dp[to]<=ans&&val[to]<=ans) {//两者都合法
if(length[to]<val[to]) dp[root]=max(dp[root],dp[to]);
else dp[root]=max(dp[root],val[to]);
length[root]+=min(length[to],val[to]);
//正常的使用两者中较小的累加到最长路,dp数组取哪个都无所谓
//因为我只要看dp数组的值有没有比ans大即可,不需要管具体值
}
else {//两者都不行,赋一个最大值,把任务交给父亲或者其他祖先完成
//如果追溯到根结点,任务依旧不能完成,就会导致length无穷大
//那么二分的时候扩大左端点
dp[root]=2000000;
length[root]=2000000;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
pushdown(1,0);
int l=1,r=1010;
int mid;
while(l<r)
{
memset(dp,0,sizeof(dp));
memset(length,0,sizeof(length));
mid=(l+r)>>1;
dfs(1,0,mid);
if(length[1]<=m) r=mid;
else l=mid+1;
}
if(r==1010) printf("-1\n");
else printf("%d\n",l);
} 总结:本题具有迷惑性,可能会弄错,如果不理解需要反复理解,在阅读完题解以后自行编码提交。



京公网安备 11010502036488号