51nod 1307 绳子与重物【并查集】【搜索】【二分法】

2 篇文章 0 订阅
1 篇文章 0 订阅
博客介绍了如何解决一个关于绳子承重和小球悬挂的问题,给出了两种方法:一是使用二分查找配合深度优先搜索(DFS),复杂度为O(nlogn);二是利用并查集,通过路径压缩实现O(Alpha(n))的时间复杂度,优于二分DFS的方案。内容包括问题描述、解题思路、代码实现以及相关优化细节。
摘要由CSDN通过智能技术生成

题目大意

传送门

给出
每个绳子的承重Ci
小球的重量Wi
小球挂在那个小球上Pi

问最多挂多少个绳子而不会出现绳子断掉的情况。

思路

本文是看教程:O(n)可以解决的2道题

习题的第二题。

思路1 二分+DFS

按照我的想法,对绳子的列表进行二分 O(logn) ,每次判断绳子会不会断掉 O(n) ,使用DFS。最后的复杂度是 O(nlogn)

思路2 并查集

使用路径压缩之后并查集的平摊复杂度为 Alpha(n) ,这是一个增长非常缓慢的函数,在int64范围内的n,可以近似看做是常数。
使用并查集+DFS的方法复杂度可以看做是 O(Alpha(n)n) 要小于 O(nlogn)

使用并查集的时候要将维护建树的过程:
首先建树的时候维护元素node[i].W_S如果以当前结点为根的子树的重量和大于了承重,就要删除最后一个元素(为什么是最后一个元素?因为重物是按照顺序添加的,可以从最后一个慢慢删,仅仅针对此题)。

代码1 二分+DFS

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;

#define INF 1<<30
#define maxn 50005
int W[maxn];
int C[maxn];
int P[maxn];
int N;

int W_Sum[maxn];//绳子i现在的承重

vector<int> G[maxn];//与绳子i相连的点的集合


//这时候物体的结构已经被储存在了vector中
bool dfs(int u)
{
    W_Sum[u] = W[u];
    for(int i = 0; i<G[u].size(); i++)
    {
        if(!dfs(G[u][i])) return 0;
        W_Sum[u] += W_Sum[G[u][i]];
    }

    return W_Sum[u] <= C[u];
}

bool Solve(int x)
{
    for(int i = 0; i <= x; i++) G[i].clear();//将物体i挂在的绳子P[i]上的物体clear
    for(int i = 1; i <= x; i++) G[P[i]].push_back(i); //物体i 挂在 绳子P[i]上

    return dfs(0);
}

int main()
{
    while(~scanf("%d",&N))
    {
        for(int i = 1; i <= N; i++)
        {
            scanf("%d%d%d",&C[i],&W[i],&P[i]),P[i]++;//分别是 绳子的最大负重 物体的重量 挂在哪个绳子上
        }
        C[0] = INF;//将根节点的最大承重设置为无限大

        int l = 0;
        int r = N;

        while(l<r)
        {
            //printf("%d %d\n",l,r);
            int mid = (l+r+1)/2;
            if(Solve(mid))
            {
                l = mid;
            }
            else r = mid-1;
        }

        printf("%d\n",l);
    }

}

代码2 并查集

#include<iostream>
#include<stdio.h>
#include<vector>
using namespace std;

#define INF 1<<30
#define maxn 50005

int N;

struct Node
{
    int C,W,P,W_S;
} node[maxn];

int fa[maxn];  //fa[i]表示结点i的父节点

//查找结点i的源头
int Find_fa(int i)
{
    if(fa[i]==i) return fa[i];
    return  fa[i] = Find_fa(fa[i]);
}

int main()
{
    while(~scanf("%d",&N))
    {
        for(int i = 1; i <= N; i++)
        {
            scanf("%d%d%d",&node[i].C, &node[i].W, &node[i].P);//分别是 绳子的最大负重 物体的重量 挂在哪个绳子上
            node[i].P++;
            node[i].W_S  = node[i].W;
            //Init
            fa[i] = i;
        }

        //
        int ans = N;
        for(int i = N; i ; i--)
        {
            while(node[i].W_S > node[i].C) //如果当前子树的重量 大于 结点的负重,就从最后一个添加的点开始删除
            {
                int k = Find_fa(ans);
                node[k].W_S -= node[ans].W;//将node[ans]从子树中去掉
                ans --;
            }
            node[node[i].P].W_S += node[i].W_S;//将以node[i]为结点的子树的总重量 加入到 以node[i]的父节点为子树的总重量 中
            fa[i] = node[i].P;//建树
        }

        printf("%d\n",ans);
    }

}

Hit

将物体由-1开始变成0开始

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值