并查集与01背包

这道题是学长的原创题:

Move The Lab


Description

The 573 lab will move. There are some chemicals, Severus should move them. But Figo only gave him a bag to save money. So Severus can only choose some of them. If some of the chemical drug are put together, they will explode, and if a and b can explode, b and c can explode, a and c can explode too. How can Severus choose the chemicals, he can be safe and the value can be highest.

Input

In the first line there are three integers v, n and m(1 ≤ n, m, k ≤ 1000).
Each of the following n lines contains two integers, indicating the space occupied by the i-th chemical and the value of it.
In each of the next m lines, there are two integer a and b, if a and b are put together, they will explode.
The input file is terminated by EOF.

Output

Output one integer, the most value Severus can take.

Sample Input

10 3 1
5 100
5 200
2 10
1 2

Sample Output

210

大意就是:实验室要搬家,其中有一部分化学药品在一起会爆炸,同时说明,A与B在一起会爆炸,B与C在一起会爆炸那么A与C在一起也会爆炸

输入W背包最大容量N种化学物品 M对会爆炸的组合;

之后会给与每个化学药品占用的空间和价值;以及M对不能再一起的组合;

其实这道题的算法部分很直接,看完题就知道是并查集和01背包,但是真正关键的在于处理dp[j](dp[j]指的是在质量为j的时候最多可以装多少价值的药品)的时候判断此时背包中已经有了多少药品,基于这种思想,我写了一个二维数组来判断以下是代码:(果断T了。。。。)

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
#define MAXN 1005
using namespace std;
int max_w,n,m;
int w[MAXN],v[MAXN];
int judge[MAXN][MAXN];
int father[MAXN];
int dp[MAXN];
int have[MAXN][MAXN];
int find_father(int x)
{
    if(x==father[x]) return x;
    else
        return father[x]=find_father(father[x]);
}
void unite(int x,int y)
{
    int xx=find_father(x);
    int yy=find_father(y);
    if(xx==yy) return;
    else
    {
        father[xx]=yy;
        return;
    }
}
bool same(int x,int y)
{
    return find_father(x)==find_father(y);
}
int DFS(int num,int wight)
{
    int temp=v[num];
    for(int i=1;i<=n;i++)
    {
        if(judge[num][i]&&have[wight][i])
        {
            temp-=v[i];
        }
    }
    return temp;
}
void change(int num,int wight1,int wight2)
{
    for(int i=1;i<=n;i++)
    {
        if(have[wight2][i])
        {
            if(judge[num][i])
            {
                have[wight1][i]=0;
            }
            else
            {
                have[wight1][i]=1;
            }
        }
    }
    have[wight1][num]=1;
}
int main()
{
    int x,y;
    while(scanf("%d%d%d",&max_w,&n,&m)!=EOF)
    {
        memset(have,0,sizeof(have));
        memset(judge,0,sizeof(judge));
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&w[i],&v[i]);
            father[i]=i;
        }
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            unite(x,y);
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=i;j<=n;j++)
            {
                if(same(i,j)) judge[i][j]=judge[j][i]=1;
            }
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=max_w;j>=w[i];j--)
            {
                int temp=DFS(i,j-w[i]);
                if(temp>0&&(dp[j-w[i]]+temp)>dp[j])
                {
                    dp[j]=dp[j-w[i]]+temp;
                    change(i,j,j-w[i]);
                }
            }
        }
        printf("%d\n",dp[max_w]);
    }
    return 0;
}

经过将近两个小时的痛苦挣扎,终于想到了正确的姿势:

经过并查集处理之后,吧每个集合中的元素单独记录下来,在DP的时候每一个集合中的元素最多拿一种就可以了;以下是AC代码:

/*Source Code
Problem: 1006	
Run Time: 69MS	Memory: 4744K
Language:C++	JudgeStatus: Accepted*/
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<iostream>
#define MAXN 1005
using namespace std;
int max_w,n,m;
int w[MAXN],v[MAXN];
int num[MAXN][MAXN];
int father[MAXN];
int dp[MAXN];
int have[MAXN];
int find_father(int x)//并查集
{
    if(x==father[x]) return x;
    else
        return father[x]=find_father(father[x]);
}
void unite(int x,int y)
{
    int xx=find_father(x);
    int yy=find_father(y);
    if(xx==yy) return;
    else
    {
        father[xx]=yy;
        return;
    }
}
bool same(int x,int y)
{
    return find_father(x)==find_father(y);
}
int Max(int a,int b)
{
    if(a>b) return a;
    else
        return b;
}
int main()
{
    int max_ans;
    int x,y;
    while(scanf("%d%d%d",&max_w,&n,&m)!=EOF)
    {
        max_ans=0;
        memset(have,0,sizeof(have));
        memset(num,0,sizeof(num));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&w[i],&v[i]);
            father[i]=i;
        }
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            unite(x,y);
        }
        for(int i=1;i<=n;i++)//以根节点的编号为集合的编号,把每个集合中的元素拿出来
        {
            int temp=find_father(i);
            num[temp][++have[temp]]=i;//其中have数组代表每一个集合的数量,num数组记录集合中的元素
        }
        for(int i=1;i<=n;i++)//DP部分
        {
            if(!have[i]) continue;
            for(int j=max_w;j>=0;j--)
            {
                for(int k=1;k<=have[i];k++)//这一个循环用于枚举集合中的每一个元素,之所以把它放在最里面就是应为每一个集合之中最多选取一个
                {
                    if(j<w[num[i][k]]) continue;
                    dp[j]=Max(dp[j],dp[j-w[num[i][k]]]+v[num[i][k]]);
                }
            }
        }
        printf("%d\n",dp[max_w]);//输出答案;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值