Time Limit: 1000MS | Memory Limit: 10000K |
Description
为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易"。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。
Input
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
要不是出现在最短路打专题中还真看不出他居然是最短路问题,理解后其实也不难。我要用大祭司的水晶或皮袄加m的金币换取酋长的女儿。而大祭司的水晶或皮袄又可以用其他的物品加n的金币换取,一直延伸到最后只要用一路上m+n+…的金币和最后一个人的物品便可以换到最终目标。很明显,物品就是图的节点,而金币便是图的权值。可使用dijkstra求出1节点到其他节点打最短权值,再加上对应物品的价值,求所有的最小值即可。
但是问题又来了,交易中有些人他不愿意要与自己等级相差太大的人的物品,哪怕隔N个人也不行。就是说只要和你交易过的人之中有一个与他等级相差太大便拒绝与你交易。本来特别简单打题被这个条件限制的看起来有点复杂了。
觉得复杂其实是因为对dis数组理解不深刻
我们可以把dis数组中的每一个元素看成一个集合 这个集合中包含了一大堆的路径和节点。一般我们只需要这些路径的之和的值。也就是说,从1号节点到n有许多路径,但是我们并不关心某一条路径或节点的详细情况,所以我们仅仅令dis[n]=m。m为路径权值之和。一开始dis数组初始化时所有集合都是空的或只有一个点。需要一次次不断的优化往集合中添加新的集合来优化总路程,也就是新源点合并到旧源点时,新源点到旧源点的边权的移交(也可理解为松弛)从大神博客看来的话觉的挺有道理。
刚才说了把dis数组的每一个元素看成一个集合,元素保存的就是这个集合中路径之和,因为我们并不关心路径中其他的数据。当然这是一般情况,在这道题中还有等级差的限制,所以需要将dis数组设置成结构体形式。
struct node{
int val;//路径之和
int min;//集合中节点等级的最小值
int max;//集合中节点等级的最大值
};
可以看到我们需要在起点到终点路径集合的中三个信息。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define inf 0x3f3f3f3f
using namespace std;
struct node{
int val;//路径之和
int min;//集合中节点等级的最小值
int max;//集合中节点等级的最大值
};
void dijkstra(int map[][105],int val[],int lev[],int m,int n)
{
node dis[105];
bool vis[105] = {false};
memset(dis,inf,sizeof dis);
for (int i = 1;i <= n; i++){
dis[i].min = dis[i].max = lev[i];//初始化的时候路径集合中根本没有边,只要对等级的最值初始化即可。
}
dis[1].val = 0;
for (int i = 1;i <= n; i++){
int min = inf,total = -1;
for (int i = 1;i <= n; i++){
if (min >= dis[i].val && !vis[i]){
min = dis[i].val;
total = i;
}
}
if (total == -1)
break;
vis[total] = true;
for (int j = 1;j <= n; j++){
if (abs(dis[j].max - dis[total].min) <= m//新源点和旧源点两个集合中最大等级和最小等级相互之间相差不能太大
&& abs(dis[j].min - dis[total].max) <= m//
&& dis[j].val > dis[total].val + map[total][j]){
dis[j].val = dis[total].val + map[total][j];
dis[j].max = dis[j].max > dis[total].max ? dis[j].max : dis[total].max;//由于向j集合中添加了新的权边,所以要更新变大后的集合
dis[j].min = dis[j].min < dis[total].min ? dis[j].min : dis[total].min;//中对大等级和最小等级
}
}
}
int min = inf;
for (int i = 1;i <= n; i++){
min = min < dis[i].val+val[i] ? min : dis[i].val+val[i];//把最小权边加上对应的物品价值就是答案。
}
cout << min << endl;
}
void init(int map[][105],int val[],int lev[])
{
for (int i = 1;i < 105; i++){
val[i] = lev[i] = inf;
for (int j = 1;j < 105; j++){
if (i == j)
map[i][j] = 0;
else
map[i][j] = inf;
}
}
}
int main ()
{
int m,n,val[105],map[105][105],lev[105];
while (~scanf ("%d%d",&m,&n)){
init(map,val,lev);
for (int i = 1;i <= n; i++){
int a,b,c;
scanf ("%d%d%d",&a,&b,&c);
val[i] = a;
lev[i] = b;
for (int j = 1;j <= c; j++){
int d,e;
scanf ("%d%d",&d,&e);
map[i][d] = e;
}
}
dijkstra(map,val,lev,m,n);
}
return 0;
}
实际上很多时候dis数组中都不是记录集合中的路径权值之和,有的时候是记录路径集合中最大打边或最小的边。