一、题目描述
昂贵的聘礼
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;
}