【代码超详解】POJ 1062 昂贵的聘礼(链式前向星表示图 + SPFA求最短路)

一、题目描述

昂贵的聘礼

Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 61619 Accepted: 18633(2019/11/3 11:54)

Description

年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:"嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。“探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。
为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易”。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。

Input

输入第一行是两个整数M,N(1 <= N <= 100),依次表示地位等级差距限制和物品的总数。接下来按照编号从小到大依次给出了N个物品的描述。每个物品的描述开头是三个非负整数P、L、X(X < N),依次表示该物品的价格、主人的地位等级和替代品总数。接下来X行每行包括两个整数T和V,分别表示替代品的编号和"优惠价格"。

Output

输出最少需要的金币数。

Sample Input

1 4
10000 3 2
2 8000
3 5000
1000 2 1
4 200
3000 2 1
4 200
50 2 0

Sample Output

5250

Source

浙江

二、算法分析说明与代码编写指导

链式前向星
这是一种比较新的图的表示方法。前向星是一种特殊的边数组。我们把边数组{<u, v>n}中的每一条边先后按照起点u、终点v排序,
并记录下以某个点为起点的所有边在数组中的起始位置和存储长度,就完成了前向星的构造。
但是这个过程需要用到快排,使得时间复杂度为O(nlogn) 。为了免去排序进而减小时间复杂度,我们采用链式前向星来记录一个图。
链式前向星至少要求一个head(有的资料写作first)数组和一个边数组e。边用结构体表示,至少包含终点、下一个储存位置两个元素。
对有权图,边的结构体中要添加一个元素表示权。
添加边<u, v>时,需要指定在边数组中写入的位置p(p>0,一般随着边的输入而递增),然后在该位置保存边:依次记录终点v、
权(对有权边)w、下一个存储位置next是当前的head[u]指向的位置。然后,令head[u] = p。
可见,head[u]储存的是边数组的某个下标,指向起点为u的最后录入的一条边。遍历以某点u为起点的全部边时,
循环变量i的初值就是head[u],就能定位到起点为u的最后录入的一条边在边数组中存储的位置。每次循环都令i = e[i].next,
就依次被导航到同起点的边的下一个存储位置。遍历到的最后一条边是最开始存入的边,下一个储存位置为0。
边数组e的第0个位置是空着的,这样就无需像网上的众多资料那样要先初始化head数组为-1(循环条件被设置为i≠-1)。
有时候,一个std::fill或者memset就能直接让代码超时(数组较大的情况下)。
Bellman-Ford算法的队列优化(SPFA):
设带权图G(V, E),边权代表边长(直接相连两点的通路的长度),定点s,数组d[|V|]代表定点s到各点的估计最短距离。
可以用链式前向星存图。设优先队列q(多用小到大的优先队列),并用bitset标记点是否已访问(已进入队列),
用额外的数组保存一个点进入队列的次数。
先记d[s] = 0,并标记已访问。将s压入q,递增s的访问次数。然后循环以下部分直到优先队列q为空:
取队首节点u,弹出,清除已访问标记。对取到的队首的任意邻接节点v,如果|sv|>|su|+|uv|即d[v]>d[u]+weight(u, v),
则令|sv|=|su|+|uv|即d[v]=d[u]+weight(u, v)。如果估计最短距离有变动,且v未进入队列,则将v压入队列q,并递增入队次数。
如果此过程任意一个点的更新次数(入队次数)达到|V|次,则存在负环。可以在每次递增后判定刚入队的点是否已更新|V|次。
弹出队首可以在取队首之后就弹出,也可以在后面弹出。
Bellman-Ford算法能检测并输出负圈,但SPFA仅能检测负圈。SPFA在最坏情况下(比如一些特别针对过的稠密图),
时间复杂度与Bellman-Ford算法同阶,为O(|V||E|)

对本题,首先需要能看出来这题的数据能够画成带权图。设探险者为0顶点,酋长(或者酋长的女儿)为1顶点。一共有N个物品,编号为i的物品的花费cost可以抽象成权为cost的边<0, i>。至于先到另一个人j处搞来价值为c1物品再去酋长那以更低的金币数c2娶到女儿,可以抽象成两条权分别为c1,c2的边<0, j>和<j, 1>。需要求解的就是d[1]。
图倒是建好了,但是还有个麻烦的地方,就是等级限制。用level数组保存每个人的等级。根据题意,探险者没有等级,但需要确保走过的整条交易路线中,最低等级和最高等级的差距不能超过M,否则就会有部分人员拒绝交易。所以,设酋长的等级为level[1]。最低等级从level[1] - M开始枚举,一直枚举到level[1]。最高等级就是最低等级加M。将最低等级和最高等级作为参数传入SPFA函数中,有n个区间要枚举就做n次SPFA,然后统计这n次SPFA中得到的最小的d[1]即为答案。
但是如果这题采用unsigned类型,就有个坑:酋长的等级level[1]比等级差距限制M要低时,level[1] - M就会反向溢出,会导致WA。所以,限制开始枚举的最低等级不小于0是一个解决方案。
如果不好确定不会反向溢出导致出错,建议采用有符号整数。虽然本题的数据都是非负的,但是在采用无符号类型的时候,我们应该Ctrl+F搜索一下整个代码的全部减号,考察减数是否有可能大于被减数导致反向溢出。至于有符号数与无符号数参与运算时,在本地调试的时候编译器就会给出提示。按编译器的要求修正即可。
如果你正在比赛,且不能保证采用无符号数时中间的任何结果都不会是负数,则应该采用有符号类型。

三、AC 代码(0 ms)

#include<cstdio>
#include<algorithm>
#include<queue>
#include<bitset>
#pragma warning(disable:4996)
using namespace std;
struct edge { unsigned to, cost, next; };
const unsigned H = 1 << 30;
edge e[10202]; bitset<101> visited;
unsigned d[101], head[101], level[101], M, N, P, X, T, V, u, v, p = 1, ans = H;
priority_queue<unsigned, vector<unsigned>, greater<unsigned> > q;
inline void AddEdge(const unsigned& u, const unsigned& v, const unsigned& w) {
	e[p].to = v, e[p].cost = w, e[p].next = head[u], head[u] = p, ++p;
}
inline void SPFA(const unsigned& lmin, const unsigned& lmax) {
	fill(d + 1, d + N + 1, H); visited.reset(); q.push(0);
	while (q.empty() == false) {
		u = q.top(); visited[u] = 0; q.pop();
		for (unsigned i = head[u]; i != 0; i = e[i].next) {
			v = e[i].to;
			if (level[v] >= lmin && level[v] <= lmax && d[v] > d[u] + e[i].cost) {
				d[v] = d[u] + e[i].cost; 
				if (visited[v] == 0) { visited[v] = 1; q.push(v); }
			}
		}
	}
}
int main() {
	scanf("%u%u", &M, &N);
	for (unsigned i = 1; i <= N; ++i) {
		scanf("%u%u%u", &P, &level[i], &X); AddEdge(0, i, P);
		for (unsigned j = 0; j < X; ++j) {
			scanf("%u%u", &T, &V); AddEdge(T, i, V);
		}
	}
	for (unsigned i = min(0u, level[1] - M); i <= level[1]; ++i) {
		SPFA(i, i + M); ans = min(ans, d[1]);
	}
	printf("%u\n", ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+详细注释.zip
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值