题目大意:
哥伦布到达美洲大陆后物资不充足,需要登录补给,因此需要向土著人购买一定的物资,他需要购买N种货物,和土著人交易的规则如下:
1. 用金币全价购买;
2. 用金币和一个玻璃珠购买(如果物品价值为k个金币,则可以用k - 1个金币和1个玻璃珠支付);
3. 用等价的物品交换;
4. 用一定量金币和一个价钱更低的物品来交换;
哥伦布发现有一个有趣的现象,就是在4.的情况中,存在这样的交换,比如物品B+k个金币来交换物品A,结果k小于A - B的差价,他把这种交换叫做bargain,因此可以利用这种现象使总支付的金币数量达到最小。
现有多个测例(测例数给出),每个测例中都会给定物品总数量N(0 < N ≤ 20),以及每种物品的绝对价格(用金币计算)Q和P(Q表示物品编号,P表示对应的金币数,其中0 < Q ≤ N,0 < P ≤ 30),接下来会给定M,表示有M个bargain(0 < M ≤ 20),对于每种bargain对应N1、N2、R,表示物品N2可以用物品N1+R金币换得,其中输入能保证N1价格比N2低并且R小于两者的差价,注意,R可能等于0。
对于每个测例,按照物品编号从小到大顺序输出每种物品的实际价格(通过各种交换方法取得该物品所支付的最小的金币数量),格式是"n p",n表示物品编号,p表示实际价格(即答案),最后再打印出一共有多少物品的实际价格等于另外两种物品实际价格之和。
注释代码:
/*
* Problem ID : POJ 3835 Columbus's bargain
* Author : Lirx.t.Una
* Language : C
* Run Time : 0 ms
* Run Memory : 160 KB
*/
#include <memory.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
//物品价格的无穷大
#define INF 31
//物品的最大数量
#define MAXGOODN 21
typedef int BOOL;
int g[MAXGOODN][MAXGOODN];//graph,即图的邻接矩阵
int dp[MAXGOODN];//dp[i],表示从源点到物品i的当前最短路径
//spfa维护量
int stk[MAXGOODN];
int vis[MAXGOODN];
BOOL
relax( int u, int v ) {
int nTmp;
nTmp = dp[u] + g[u][v];
if ( nTmp < dp[v] ) {
dp[v] = nTmp;
return TRUE;
}
return FALSE;
}
int
main() {
int t;//测例数
int n, m;//物品数量和bargain数量
int i, j, k;//计数变量
int no, _no, p;//N1、N2、R
int top;//栈顶
int tot;//实际价格等于另两个物品实际价格的物品的总数
int u, v;//u -> v
BOOL fnd;//find,表示是否找到实际价格等于另两个物品实际价格的物品
scanf("%d", &t);
while ( t-- ) {
scanf("%d", &n);
//交易的过程实际上就是从起始经过一次次交换换得最终物品的一条路径
//路径上的边就是支付的金币数量,物品编号就是图的结点
//由于起始时手里一件物品都没有,因此源点就表示一种什么都没有(空空如也)的状态而已
//实际价格就是寻找一条从源点到该物品的一条最短路径
for ( i = 1; i <= n; i++ )//都初始化为不可达
for ( j = 1; j <= n; j++ )
g[i][j] = INF;
//构造可交换的边
//条件2.(包含了条件1.)
for ( i = 1; i <= n; i++ ) {
scanf("%d%d", &no, &p);
g[0][no] = p - 1;//开始时,即购买第一件物品用金币+1玻璃珠最划算
}
scanf("%d", &m);
//条件4.
while ( m-- ) {//将所有bargain的边都连起来
scanf("%d%d%d", &no, &_no, &p);
g[no][_no] = p;//表示no + p可以换的_no
}
//条件3.
for ( i = 1; i < n; i++ )
for ( j = i + 1; j <= n; j++ )
if ( g[0][i] == g[0][j] ) {//等价货物交换不需要金币
g[i][j] = 0;
g[j][i] = 0;
}
//SPFA
memset(vis, FALSE, sizeof(vis));
dp[0] = 0;
for ( i = 1; i <= n; i++ ) dp[i] = INF;
stk[1] = 0;
top = 1;
while ( top ) {
u = stk[top--];
vis[u] = FALSE;
for ( v = 1; v <= n; v++ )
if ( g[u][v] != INF && relax( u, v ) && !vis[v] ) {
vis[v] = TRUE;
stk[++top] = v;
}
}
for ( i = 1; i <= n; i++ )//输出各个物品的实际价格
printf("%d %d\n", i, dp[i]);
//寻找实际价格等于另两个物品实际价格的物品的总数
tot = 0;
for ( k = 1; k <= n; k++ ) {
fnd = FALSE;
for ( i = 1; i < n; i++ ) {
for ( j = i + 1; j <= n; j++ )
if ( k != i && k != j && dp[k] == dp[i] + dp[j] ) {
tot++;
fnd = TRUE;
break;//找到后跳出循环,否则会重复计算
}
if ( fnd ) break;
}
}
printf("%d\n", tot);
}
return 0;
}
无注释代码:
#include <memory.h>
#include <stdio.h>
#define TRUE 1
#define FALSE 0
#define INF 31
#define MAXGOODN 21
typedef int BOOL;
int g[MAXGOODN][MAXGOODN];
int dp[MAXGOODN];
int stk[MAXGOODN];
int vis[MAXGOODN];
BOOL
relax( int u, int v ) {
int nTmp;
nTmp = dp[u] + g[u][v];
if ( nTmp < dp[v] ) {
dp[v] = nTmp;
return TRUE;
}
return FALSE;
}
int
main() {
int t;
int n, m;
int i, j, k;
int no, _no, p;
int top;
int tot;
int u, v;
BOOL fnd;
scanf("%d", &t);
while ( t-- ) {
scanf("%d", &n);
for ( i = 1; i <= n; i++ )
for ( j = 1; j <= n; j++ )
g[i][j] = INF;
for ( i = 1; i <= n; i++ ) {
scanf("%d%d", &no, &p);
g[0][no] = p - 1;
}
scanf("%d", &m);
while ( m-- ) {
scanf("%d%d%d", &no, &_no, &p);
g[no][_no] = p;
}
for ( i = 1; i < n; i++ )
for ( j = i + 1; j <= n; j++ )
if ( g[0][i] == g[0][j] ) {
g[i][j] = 0;
g[j][i] = 0;
}
memset(vis, FALSE, sizeof(vis));
dp[0] = 0;
for ( i = 1; i <= n; i++ ) dp[i] = INF;
stk[1] = 0;
top = 1;
while ( top ) {
u = stk[top--];
vis[u] = FALSE;
for ( v = 1; v <= n; v++ )
if ( g[u][v] != INF && relax( u, v ) && !vis[v] ) {
vis[v] = TRUE;
stk[++top] = v;
}
}
for ( i = 1; i <= n; i++ )
printf("%d %d\n", i, dp[i]);
tot = 0;
for ( k = 1; k <= n; k++ ) {
fnd = FALSE;
for ( i = 1; i < n; i++ ) {
for ( j = i + 1; j <= n; j++ )
if ( k != i && k != j && dp[k] == dp[i] + dp[j] ) {
tot++;
fnd = TRUE;
break;
}
if ( fnd ) break;
}
}
printf("%d\n", tot);
}
return 0;
}
单词解释:
bead:n, 珠子
savage:n, 野蛮人,土著人
seldom:adv, 很少
continent:n, 洲,大陆
voyage:n, 航行,航程
Frontera:n, 弗隆特拉,人名
Palos:n, 帕罗斯,地名,西班牙南部港口
depart:vi, 启程,出发
Columbus:n, 哥伦布,人名
Christopher:n, 克里斯多夫,人名
August:n, 八月