51nod 1307 绳子与重物【二分+Dfs】

通过二分查找和图遍历解决绳子悬挂问题,确保不超过最大承重。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:  Codility
基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
有N条绳子编号 0 至 N - 1,每条绳子后面栓了一个重物重量为Wi,绳子的最大负重为Ci。每条绳子或挂在别的绳子下或直接挂在钩子上(编号-1)。如果绳子下所有重物的重量大于绳子的最大负重就会断掉(等于不会断)。依次给出每条绳子的负重Ci、重物的重量Wi以及绳子会挂在之前的哪条绳子的下面,问最多挂多少个绳子而不会出现绳子断掉的情况。

例如下图:

5, 2, -1
3, 3, 0
6, 1, -1
3, 1, 0
3, 2, 3



挂到第4个时会有绳子断掉,所以输出3。


Input
第1行:1个数N,表示绳子的数量(1 <= N <= 50000)。
第2 - N + 1行:每行3个数,Ci, Wi, Pi,Ci表示最大负重,Wi表示重物的重量,Pi表示挂在哪个绳子上,如果直接挂在钩子上则Pi = -1(1 <= Ci <= 10^9,1 <= Wi <= 10^9,-1 <= Pi <= N - 2)。
Output
输出1个数,最多挂到第几个绳子,不会出现绳子断掉的情况。
Input示例
5
5 2 -1
3 3 0
6 1 -1
3 1 0
3 2 3
Output示例
3

思路:


1、首先我们知道,挂的绳子越多,越有可能爆炸,那么对应我们这里就有一个单调性,我们可以二分枚举一个当前值mid进行判断,如果可行,那么对应我们就要判断更多的绳子是否可行,否则就判断少一些的绳子是否可行,对于绳子数量,我们进行一次整体二分。


2、对应枚举出来的当前值mid,我们建立其mid条有向边,对应我们将0设定为根节点,连接钩子的绳子我们都将其连接到根节点上,对应第i条绳子尾部的节点我们都设定为编号i即可。

建好图之后,我们设定一个数组sum【i】,表示i号节点的总承重量,那么接下来我们从根节点开始Dfs.统计各个点的承重量即可。

接下来对每个节点的承重量进行判断,如果超额了,那么对应当前情况就是不可行的,否则就是可行的。


3、数据范围比较大,在统计sum的过程中会爆int,所以数据范围要注意一下。


Ac代码:

#include<stdio.h>
#include<string.h>
using namespace std;
#define ll __int64
struct node
{
    int from;
    int to;
    int w;
    int next;
}e[500006];
int degree[50006];
ll c[50006];
ll w[50006];
ll sum[500006];
int p[50006];
int vis[50006];
int head[50006];
int n,cont,flag;
void add(int from,int to)
{
    e[cont].to=to;
    e[cont].next=head[from];
    head[from]=cont++;
}
ll Dfs(int u)
{
    sum[u]+=w[u];
    for(int i=head[u];i!=-1;i=e[i].next)
    {
        int v=e[i].to;
        sum[u]+=Dfs(v);
    }
    return sum[u];
}
int Slove(int mid)
{
    cont=0;
    memset(sum,0,sizeof(sum));
    memset(head,-1,sizeof(head));
    for(int i=1;i<=mid;i++)
    {
        int v;
        if(p[i]==-1)v=0;
        else v=p[i]+1;
        add(v,i);
    }
    Dfs(0);
    for(int i=1;i<=mid;i++)
    {
        if(sum[i]>c[i])return 0;
    }
    return 1;
}
int main()
{
    while(~scanf("%d",&n))
    {
        memset(c,0,sizeof(c));
        memset(w,0,sizeof(w));
        memset(p,0,sizeof(p));
        for(int i=1;i<=n;i++)
        {
            scanf("%I64d%I64d%d",&c[i],&w[i],&p[i]);
        }
        int l=1;
        int r=n;
        int ans=0;
        while(r-l>=0)
        {
            int mid=(l+r)/2;
            if(Slove(mid)==1)
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
}

最后再上一发时间榜第一名选手的代码(并查集写成这么精简真的值得学习):






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值