搭配购买tj

Joe觉得云朵很美,决定去山上的商店买一些云朵。商店里有n多云,云朵被编号为1,2,.....n,并且每朵云都有一个价值。
但是商店老板跟他说,一些云朵要搭配来买才好,所以买一朵云则与这朵云有搭配的云都要买。
但是Joe的钱有限,所以他希望买的价值越多越好。


题目概述

有n个点,m条线将它们中的一部分连接,每个点有它的价值和权值。
获得这个点要付出它的权值并获得与它连接的点,求在付出不超过w时,可以获得的最大价值。

输入

第1行:n、m、w,表示n多云,m个搭配,Joe有w的钱

第2至n+1行,每行ci,di表示i朵云的价钱和价值。

第n+2至n+1+m行,每行ui,vi表示买ui就必须买vi,同理,如果买vi就必须买ui。

输出

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

思路

如下图,有n个点,m条边,并且是双向的,很容易联想到图,
将问题转化为求每个连通块的价值和付出的权值,
再套上01背包模板求最大价值。

代码实现

可以用并查集来储存,然后缩点:

for(int i=1; i<=n; i++)
	{
		int v=find(i);
		if(!vis[v])
		{
			cnt++;
			vis[v]=cnt;
		}
		c[vis[v]]+=e[i].x;
		w[vis[v]]+=e[i].y;
	}

但这里我用递推来寻找连通块:

void find(int u)
{
	for(auto it=v[u].begin();it!=v[u].end();it++)//v[i]用于储存以i为定点的连通块所包含的点
	{
		if(vis[*it]==true)//如果这个点之前已经寻找过,那就跳过,避免重复计算
		{
			continue;
		}
		vis[*it]=true;
		nc+=a[*it].c;//nc是这个连通块需付出的权值总和
		nd+=a[*it].d;//nd是连通块可以获得的价值总和
		find(*it);//递归查找
	}
}

Ac Code

#include <bits/stdc++.h>
using namespace std;
//分割线qwq
int n,m,w;
int nc=0,nd=0;
int cnt;
bool vis[10005];
vector<int> v[10005];
int dp[100005];
//分割线qwq
struct yun
{
	int c;
	int d;
}a[10005];
struct cd
{
	int c;
	int d;
}cdd[10005];
//分割线qwq
void find(int u)
{
	for(auto it=v[u].begin();it!=v[u].end();it++)
	{
		if(vis[*it]==true)
		{
			continue;
		}
		vis[*it]=true;
		nc+=a[*it].c;
		nd+=a[*it].d;
		find(*it);
	}
}
//分割线qwq
int main()
{
	cin>>n>>m>>w;
	for(int i=1;i<=n;i++)
	{
		int c,d;
		cin>>c>>d;
		a[i].c=c;
		a[i].d=d;
	}
	for(int i=1;i<=m;i++)
	{
		int x,y;
		cin>>x>>y;
		v[x].push_back(y);
		v[y].push_back(x);
	}
	for(int i=1;i<=n;i++)
	{
		if(vis[i]==false)
		{
			nc=nd=0;
			vis[i]=true;
			nc+=a[i].c;
			nd+=a[i].d;
			find(i);
			if(nc<=w)
			{
				cnt++;
				cdd[cnt].c=nc; 
				cdd[cnt].d=nd;
			}
		}
	}
	for(int i=1;i<=cnt;i++)
	{
		for(int j=w;j>=cdd[i].c;j--)
		{
			dp[j]=max(dp[j],dp[j-cdd[i].c]+cdd[i].d);
		}
	}
	cout<<dp[w];//01背包,作者不会不会吧qwq
	return 0;
}

并查集代码(感谢dcz提供)

#include <bits/stdc++.h>
using namespace std;
int n,m,dp[200001],pre[200001],k,c[200001],w[200001],cnt,ans,vis[100001];
struct node
{
	int x,y;
} e[200001];
int find(int x)
{
	return x==pre[x]?x:pre[x]=find(pre[x]);
}
void merge(int x,int y)
{
	int rx=find(x),ry=find(y);
	if(rx!=ry)
	{
		pre[ry]=rx;
	}
}
int main()
{
	cin>>n>>m>>k;
	for(int i=1; i<=n; i++)
	{
		cin>>e[i].x>>e[i].y;
		pre[i]=i;
	}
	for(int i=1; i<=m; i++)
	{
		int x,y;
		cin>>x>>y;
		merge(x,y);
	}
	for(int i=1; i<=n; i++)
	{
		int v=find(i);
		if(!vis[v])
		{
			cnt++;
			vis[v]=cnt;
		}
		c[vis[v]]+=e[i].x;
		w[vis[v]]+=e[i].y;
	}
	for(int i=1; i<=cnt; i++)
	{
		for(int j=k; j>=c[i]; j--)
		{
			dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
			ans=max(ans,dp[j]);
		}
	}
	cout<<ans;
	return 0;
}

总结一下

这是一道大水题,考点不明,只要注意细节就可以AC
正解仿佛是Tarjan,作者懒,改天补上

posted @ 2023-08-18 16:26  Funny_Cat  阅读(71)  评论(0)    收藏  举报
一键三连