【C++/并查集+01背包】搭配购买

178 阅读3分钟

题目描述

明天就是母亲节了,电脑组的小朋友们在忙碌的课业之余挖空心思想着该送什么礼物来表达自己的心意呢?听说在某个网站上有卖云朵的,小朋友们决定一同前往去看看这种神奇的商品,这个店里有 n 朵云,云朵已经被老板编号为 1,2,3,...,n,并且每朵云都有一个价值,但是商店的老板是个很奇怪的人,他会告诉你一些云朵要搭配起来买才卖,也就是说买一朵云则与这朵云有搭配的云都要买,电脑组的你觉得这礼物实在是太新奇了,但是你的钱是有限的,所以你肯定是想用现有的钱买到尽量多价值的云。

输入格式

第一行输入三个整数,n,m,w,表示有 n 朵云,m 个搭配和你现有的钱的数目。

第二行至 n+1 行,每行有两个整数, ci​,di​,表示第 i 朵云的价钱和价值。

第 n+2 至 n+1+m 行 ,每行有两个整数 ui​,vi​。表示买第 ui​ 朵云就必须买第 vi​ 朵云,同理,如果买第 vi​ 朵就必须买第 ui​ 朵。

输出格式

一行,表示可以获得的最大价值。

输入输出样例

输入 #1

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

输出 #1

1

说明/提示

  • 对于 30% 的数据,满足 1≤n≤100;
  • 对于 50% 的数据,满足 1≤n,w≤10^3,1≤m≤100;
  • 对于 100% 的数据,满足 1≤n≤10^4,0≤m≤5×10^3。

思路

通过题目中云朵的价值与价格,然后自己又有限定的钱的数目,要求所购商品的最大价值,且云朵只能购买一次,可知为01背包。后续条件中已知,对于每个编号,如果1和3绑定、3和2绑定、4和2绑定,那么在购买2号云朵的同时就会购买1号、3号、4号,其他同理,很显然是一个并查集的知识点,将编号化为一个个节点,通过无向边将每个节点连接起来,对于绑定在一起的云朵,就可以将它们一起看作是一个大云朵,这个云朵的价值与价格分别是绑定起来的小云朵的价值与价格相加之和。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;

int set1[100000],a[100000],b[100000];

int find(int x)//查找父级节点
{
    if(set1[x]==x)
        return x;
    return  set1[x] = find(set1[x]);
}

void merge(int x,int y)//【1】将x和y连接起来
{

    int fx=find(x);
    int fy=find(y);
    if(fy!=fx)
    {
        set1[fy]=fx;
    }
}

int main()
{
    int n,m,w;
    cin>>n>>m>>w;
    vector<int> dp(1000000),u(1000000),v(1000000);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
        set1[i]=i;//初始化set1  让这个集合里每个元素都是自己的父节点
                  //set1中,下标是编号,值为该编号的父节点
    }

    for(int i=1;i<=m;i++)
    {
        cin>>u[i]>>v[i];
        merge(u[i],v[i]);
        //set1[find(u[i])]=find(v[i]);//【2】此处代码可代替【1】处的功能
    }

    for(int i=1;i<=n;i++)//将小云朵整合成一个大云朵
    {
        if(set1[i]!=i)
        {
            a[find(i)]+=a[i];
            b[find(i)]+=b[i];
            a[i]=b[i]=0;
        }
    }

    for(int i=1;i<=n;i++)//01背包
    {
        for(int j=w;j>=a[i];j--)
        {
            dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
        }
    }
    cout<<dp[w]<<endl;
    return 0;
}

//2022.4.27