初识并查集(写给自己看的记录)

本来在a代码,但是B题用自己的方式a了好几次a不出来,然后就去网上搜索,得到了一个代码,放入vj里面ac了,于是进行研究,发现了一段看不懂的代码

int find(int x)//找代表值
{
	if(x==f[x]) return x;
	return f[x]=find(f[x]);
}

很明显,一个递归代码,但是不知道起到什么作用,于是csdn了一下

并查集,一种特殊的数据结构(它的逻辑结构本质也是一颗“树”,有唯一的根节点,任意数的子节点),它的特殊在于它只定义了两种数据操作(查找和合并)。这是用来解决连通性问题,查找(find):就是查找任意两个节点是否连通,就是是否有共同的祖先(节点找它的父节点的过程,一层一层地找)。合并(union):把两个不同集合的节点合并在一起。
————————————————
版权声明:本文为CSDN博主「帅气小魔王」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44630682/article/details/106218408

翻阅了一下购买的算法笔记,发现并查集有3部分组成
1.初始化

forint i=1;i<=N;i++)
{
	father[i]=i;//令father[i]=-1也可,此处以father[i]=1为例
}

2.查找
由于规定同一个集合中只存在一个根结点,因此查找操作就是对给定的结点寻找其根结点的过程。实现的方式可以是递推或是递归,但是其思路都是一样的,即反复寻找父亲结点,直到寻找到根结点为止(即father[i]==i的结点)
递推代码:

int findfather (int x)
{
	while (x!=father[i])
	{
		x=father[x];
	}
	return x;
}

递归代码:

int findfather
{
	if(x==father[x])
		return x;
	else
		return findfather (father[x]);
}

3.合并(这是整个并查集最关键的环节,也是并查集的关键目的)
合并操作的目的是将两个集合合并成一个集合,题目中一班给出两个元素,要求把这两个元素所在的集合合并。具体实现上一般是先判断两个元素是否属于同一个集合,只有当两个元素属于不同的集合时才合并,而合并的过程一般是把其中一个集合的根结点的父亲指向另一个集合的根结点。
①查找两个元素a、b的根结点是否相同,判断他们是否属于同一个集合。
②合并两个集合,a的父亲根结点指向b的父亲根结点(反过来是一样的,没有区别)
上代码!

void Union (int a,int b)
{
	int fatherA = findfather (a);
	int fatherB = findfather (b);
	if(fatherA!=fatherB)
	{
		father[fatherA] = fatherB;
	}
}

提出问题的源代码为

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct jgt
{
	int d,p;
}a[10010];
int n,f[10010];
int find(int x)//找代表值
{
	if(x==f[x]) return x;
	return f[x]=find(f[x]);
}
bool cmp(jgt t1,jgt t2)
{
	return t1.p>t2.p;
}
int main()
{
	int mx,ans,i,t;
	while(cin>>n)
	{
		for(mx=0,i=0;i<n;mx=max(mx,a[i].d),i++)
			scanf("%d%d",&a[i].p,&a[i].d);
		for(i=0;i<=mx;f[i]=i,i++);
		sort(a,a+n,cmp);//先按收益排序 
		for(ans=0,i=0;i<n;i++)
		{
			t=find(a[i].d);
			if(t>0)
			{
				ans+=a[i].p;//加收益 
				f[t]=t-1;//剩余过期时间-1 
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

【POJ】1456 supermarket
代码转载:https://blog.csdn.net/weixin_46975572/article/details/112986824
最后让我们来讨论这位大佬为什么要用并查集来做这道题
原题目

A supermarket has a set Prod of products on sale. It earns a profit px for each product x∈Prod sold by a deadline dx that is measured as an integral number of time units starting from the moment the sale begins. Each product takes precisely one unit of time for being sold. A selling schedule is an ordered subset of products Sell ≤ Prod such that the selling of each product x∈Sell, according to the ordering of Sell, completes before the deadline dx or just when dx expires. The profit of the selling schedule is Profit(Sell)=Σx∈Sellpx. An optimal selling schedule is a schedule with a maximum profit. For example, consider the products Prod={a,b,c,d} with (pa,da)=(50,2), (pb,db)=(10,1), (pc,dc)=(20,2), and (pd,dd)=(30,1). The possible selling schedules are listed in table 1. For instance, the schedule Sell={d,a} shows that the selling of product d starts at time 0 and ends at time 1, while the selling of product a starts at time 1 and ends at time 2. Each of these products is sold by its deadline. Sell is the optimal schedule and its profit is 80.

超级市场有一套特价商品。它为每个产品x∈Prod按截止日期dx销售赚取利润px,该截止日期dx是从销售开始的时间单位的整数。每种产品的销售都需要一个单位的时间。销售计划是产品Sell≤Prod的一个有序子集,使得每个产品x∈Sell的销售根据Sell的顺序在截止日期dx之前或dx到期时完成。销售计划的利润是利润(Sell)=∑x∈Sellpx。最优销售计划是利润最大化的计划。
例如,考虑产品Prod={a,b,c,d},其中(pa,da)=(50,2),(pb,db)=(10,1),(pc,dc)=(20,2),(pd,dd)=(30,1)。表1列出了可能的销售时间表。例如,schedule Sell={d,a}显示产品d的销售从时间0开始到时间1结束,而产品a的销售从时间1开始到时间2结束。每一种产品都在截止日期前售出。销售是最优方案,利润为80%。

Write a program that reads sets of products from an input text file and computes the profit of an optimal selling schedule for each set of products.

编写一个程序,从输入文本文件中读取产品集,并计算每套产品的最佳销售计划的利润。

Input

A set of products starts with an integer 0 <= n <= 10000, which is the number of products in the set, and continues with n pairs pi di of integers, 1 <= pi <= 10000 and 1 <= di <= 10000, that designate the profit and the selling deadline of the i-th product. White spaces can occur freely in input. Input data terminate with an end of file and are guaranteed correct.

一组产品以整数0<=n<=10000开始,这是该组产品的数量,并以整数的n对pi di、1<=pi<=10000和1<=di<=10000继续,指定第i个产品的利润和销售期限。空白可以在输入中自由出现。输入数据以文件结尾终止,并保证正确.
Output

For each set of products, the program prints on the standard output the profit of an optimal selling schedule for the set. Each result is printed from the beginning of a separate line.
Sample Input
4 50 2 10 1 20 2 30 1
7 20 1 2 1 10 3 100 2 8 2
5 20 50 10

对于每一套产品,程序在标准输出上打印出该套产品的最佳销售计划的利润。每个结果从一行开始打印。
样本输入

Sample Output
80
185
Hint

The sample input contains two product sets. The first set encodes the products from table 1. The second set is for 7 products. The profit of an optimal schedule for these products is 185.

示例输入包含两个产品集。第一组对表1中的产品进行编码。第二套是7种产品。这些产品的最优调度的利润是185。

咱脑子笨,但是咱又不是没脑子,虽然当前大佬用并查集优化这道题的方式我看不懂,但是已经明白了这道题是在用并查集优化,这就提供给我了线索,在苦苦思考加搜集资料后,终于整明白了。
在这里使用并查集,主要是对于每件商品的过期日期来说的。如果用并查集来优化改程序,我们在制定贪心规则时就会简单很多,只按商品价值的大小进行排序。并查集的作用是当做一个记录到当前截止时间段的时候,我要在第几天卖出这件商品。
借鉴老哥代码

int Find(int k)
{
    if(k==pre[k])
        return k;
    else
        return pre[k] = Find(pre[k]);
}
/*查找函数*/
 for(int i=0;i<=maxn;i++)
        pre[i] = i;
        /*初始化*/
for(int i=0;i<n;i++)
    {
        int f = Find(a[i].t)//找到最后期限,然后日期给f
        if(f>0)//r如果在天数为0之前,那么
        {
             pre[f] = f - 1;/*他的根节点,也就是卖出去的日期数就要-1,
             这样,下次遇到相同结束日期的商品的时候,它的根节点的值,
            也就是卖出去的日期数就是现在的f-1*/
            ans += a[i].w;
        }
    }
————————————————
版权声明:本文为CSDN博主「mrxs」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/FEATHER2016/article/details/79202730

这样的话,日期的问题就没有那么复杂了,按照价值大小进行排序,将截止日期的代表值对应到储存截止日期的数组中。从价值高的开始卖,如果数组中储存的截止日期不为零,就相当于将这件商品卖出去了。(并查集的作用是将相同日期的商品建立成一个集合,并将这个集合的父亲结点数值作为数组下标,对应到储存日期的数组中,这样遇到相同商品时,截止日期就变成了x-1)
问题到这里,我感觉明白了不少,但是又出现了一个问题困扰我:
如果相同截止日期的商品被并查集联系在了一起,而不同截止日期的商品的截止日期不同,又无法统一进行操作该怎么办呢。
这样这个问题岂不是变成了并行式的,而题目明显是一道以时间为轴线思考的题目,这明显不符合题意。思考良久,我决定去程序中试试。
神奇的事情发生了
4 50 2 10 1 20 2 30 1
2
1
0
0
80
将截止日期输出,发现并不是我想的那样,所有物品的截止日期都在递减。如第一件商品截止日期是2,而第3件商品同样是2,但是现有时间已经变成了0,无法再卖出去。
再来一组
7 20 1 2 1 10 3 100 2 8 2
5 20 50 10
2
10
1
3
0
20
0
185
于是我建立了一个并查集程序,查看数字集合之间的关系,结果一无所获,没什么用。
到这实在想不出来了,使用的时间太多,暂时放下,之后慢慢想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值