题目大意:
定义一棵树的比率:即所有边权值之和比上所有点权值之和。
现有一个完全图(各点之间连通)共有n个点(2 ≤ n ≤ 15),现要出其具有m个点(2 ≤ m ≤ n)的子树(同时也是子图),其比率是所有子树中比率最小的。
现有多个测例,每个测例中给出n和m(输入以n = 0结束),接着给出每个点的权值,再接着给出一个n × n的边权值矩阵,所有权值的范围为[1, 100],并且点是从1开始编号的,求其最优比例生成树,要求按照从小到大顺序打印该树的点序号,如果有多个解则只输出字典序最小的那个解。
注释代码:
/*
* Problem ID : POJ 3925 Minimal Ratio Tree
* Author : Lirx.t.Una
* Language : C++
* Run Time : 16 ms
* Run Memory : 148 KB
*/
#include <memory.h>
#include <stdio.h>
#include <math.h>
//思路:指定树的结点数后,由于是完全图(任意两点之间有通路)
//所以任意m个点都可以组成一个生成树
//不妨先对m个点进行枚举,比如枚举出了一组点,那么这组点的点权值就固定了
//如果想让其 边权/点权 最小就必须得让边权最小,那么就得求最小生成树
//因此总问题就转化成了求所有m个点的最小生成树中比例最小的了
//代码中的select组合函数可以使得求出的m个点的组合按照点的编号从小到大排列
#define TRUE 1
#define FALSE 0
#define INF 101
//float INF
//浮点数的无穷大,越大越好吧
#define FINF 1E10
#define ESP 1E-8
//最大点数
#define MAXN 16
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
typedef char BOOL;
int tree[MAXN];//保存最小生成树中结点的编号,编号从1开始计
int ans[MAXN];//保存最终结果的最小生成树
int wn[MAXN];//weight of node,各结点的权值
int g[MAXN][MAXN];//graph,保存图的任意两点之间的距离
int dist[MAXN];//Prim算法中当前最短距离
BOOL vist[MAXN];//Prim算法中判断一个点是否被访问过
double mrtio;//minimum ratio,保存当前最小的比率(即最小生成树的边权/点权)
void
prim(int m) {//m为最小生成树结点数
int i;
int min_dist;
int min_node;
int tot_we;//totoal weight of edge,边权总和
int tot_wn;//total weight of node,点权总和
int nvist;//number of visited node,被访问过的点数
double rtio;//ratio,比例,临时变量
//正常的Prim算法
memset(vist, FALSE, sizeof(vist));
vist[1] = TRUE;
nvist = 1;
dist[1] = 0;
for ( i = 2; i <= m; i++ ) dist[i] = g[ tree[1] ][ tree[i] ];
for ( tot_wn = 0, i = 1; i <= m; i++ )
tot_wn += wn[ tree[i] ];
tot_we = 0;
while ( nvist < m ) {
for ( min_dist = INF, i = 2; i <= m; i++ )
if ( !vist[i] && dist[i] < min_dist ) {
min_dist = dist[i];
min_node = i;
}
vist[min_node] = TRUE;
tot_we += dist[min_node];
nvist++;
for ( i = 2; i <= m; i++ )
if ( !vist[i] )
dist[i] = MIN( dist[i], g[ tree[min_node] ][ tree[i] ] );
}
//Prim算法结束
//判断当前最小生成树是否为比例最优的
rtio = (double)tot_we / (double)tot_wn;
if ( fabs( rtio - mrtio ) > ESP && rtio < mrtio ) {//注意!!一定不能少fabs( rtio - mrtio ) > ESP
//因为有可能出现相等的情况,题目中要求相等情况中取字典序最小的
//前面的组合一定比当前组合字典序小!!!
memcpy(ans, tree, sizeof(ans));
mrtio = rtio;
}
}
void
select( int bg, int nd, int n, int m ) {//求m点的所有组合
//begin,其实筛选位置
//done number,当前已经筛选出来的结点数
//n为总点数
//m为最小生成树的点数
if ( nd == m ) {//如果已经全部筛选完,则可以直接调用Prim算法求比率
prim(m);
return ;
}
int i;
for ( i = bg; i <= n; i++ ) {//否则就从begin开始筛选
tree[nd + 1] = i;
select( i + 1, nd + 1, n, m );//注意!!一定要从i + 1位置开始筛选
//这样可以保证筛选出来的序列是按照编号升序的!!!
}
}
int
main() {
int n, m;//图结点数和最小生成树结点数
int i, j;//计数变量
while ( scanf("%d%d", &n, &m), n ) {
for ( i = 1; i <= n; i++ )
scanf("%d", wn + i);
for ( i = 1; i <= n; i++ )
for ( j = 1; j <= n; j++ )
scanf("%d", &g[i][j]);
mrtio = FINF;
select( 1, 0, n, m );
printf("%d", ans[1]);
for ( i = 2; i <= m; i++ ) printf(" %d", ans[i]);
putchar('\n');
}
return 0;
}
无注释代码:
#include <memory.h>
#include <stdio.h>
#include <math.h>
#define TRUE 1
#define FALSE 0
#define INF 101
#define FINF 1E10
#define ESP 1E-8
#define MAXN 16
#define MIN(x,y) ( (x) < (y) ? (x) : (y) )
typedef char BOOL;
int tree[MAXN];
int ans[MAXN];
int wn[MAXN];
int g[MAXN][MAXN];
int dist[MAXN];
BOOL vist[MAXN];
double mrtio;
void
prim(int m) {
int i;
int min_dist;
int min_node;
int tot_we;
int tot_wn;
int nvist;
double rtio;
memset(vist, FALSE, sizeof(vist));
vist[1] = TRUE;
nvist = 1;
dist[1] = 0;
for ( i = 2; i <= m; i++ ) dist[i] = g[ tree[1] ][ tree[i] ];
for ( tot_wn = 0, i = 1; i <= m; i++ )
tot_wn += wn[ tree[i] ];
tot_we = 0;
while ( nvist < m ) {
for ( min_dist = INF, i = 2; i <= m; i++ )
if ( !vist[i] && dist[i] < min_dist ) {
min_dist = dist[i];
min_node = i;
}
vist[min_node] = TRUE;
tot_we += dist[min_node];
nvist++;
for ( i = 2; i <= m; i++ )
if ( !vist[i] )
dist[i] = MIN( dist[i], g[ tree[min_node] ][ tree[i] ] );
}
rtio = (double)tot_we / (double)tot_wn;
if ( fabs( rtio - mrtio ) > ESP && rtio < mrtio ) {
memcpy(ans, tree, sizeof(ans));
mrtio = rtio;
}
}
void
select( int bg, int nd, int n, int m ) {
if ( nd == m ) {
prim(m);
return ;
}
int i;
for ( i = bg; i <= n; i++ ) {
tree[nd + 1] = i;
select( i + 1, nd + 1, n, m );
}
}
int
main() {
int n, m;
int i, j;
while ( scanf("%d%d", &n, &m), n ) {
for ( i = 1; i <= n; i++ )
scanf("%d", wn + i);
for ( i = 1; i <= n; i++ )
for ( j = 1; j <= n; j++ )
scanf("%d", &g[i][j]);
mrtio = FINF;
select( 1, 0, n, m );
printf("%d", ans[1]);
for ( i = 2; i <= m; i++ ) printf(" %d", ans[i]);
putchar('\n');
}
return 0;
}
单词解释:
minimal:adj, 最小的
weight:n, 权值; vt, 赋予权值
equation:n, 方程式,等式
diagonal:n, 对角线,斜线; adj, 对角线的,斜线的
symmetrical:adj, 对称的
connectivity:n, 连通性(数学术语)
tie:n, 平局
ratio:n, 比率,比例