Minimal Ratio Tree
For a tree, which nodes and edges are all weighted, the ratio of it is calculated according to the following equation.
Given a complete graph of n nodes with all nodes and edges weighted, your task is to find a tree, which is a sub-graph of the original graph, with m nodes and whose ratio is the smallest among all the trees of m nodes in the graph.
Input
Input contains multiple test cases. The first line of each test case contains two integers n (2<=n<=15) and m (2<=m<=n), which stands for the number of nodes in the graph and the number of nodes in the minimal ratio tree. Two zeros end the input. The next line contains n numbers which stand for the weight of each node. The following n lines contain a diagonally symmetrical n×n connectivity matrix with each element shows the weight of the edge connecting one node with another. Of course, the diagonal will be all 0, since there is no edge connecting a node with itself.
All the weights of both nodes and edges (except for the ones on the diagonal of the matrix) are integers and in the range of 1,1001,100.
The figure below illustrates the first test case in sample input. Node 1 and Node 3 form the minimal ratio tree.
Output
For each test case output one line contains a sequence of the m nodes which constructs the minimal ratio tree. Nodes should be arranged in ascending order. If there are several such sequences, pick the one which has the smallest node number; if there’s a tie, look at the second smallest node number, etc. Please note that the nodes are numbered from 1 .
Sample Input
3 2
30 20 10
0 6 2
6 0 3
2 3 0
2 2
1 1
0 2
2 0
0 0
Sample Output
1 3
1 2
题意:有n个点,每个点有个权值,任意两个点之间有一条边,同样也有权值。然后要们从这n个点中选取m个点,是得这m个点组成一颗生成树,然后使得这颗生成树中边的权和与所有点的权和的比值最小。
分析:对于选定的m个点,我们可以知道,所有节点的权和是一定的,所以要使得ratio尽可能小,所以就相当于边的权和尽可能的小,所以就是求这m个点的最小生成树,我们可以注意到n的数目最大只有15,m最大只有10,所以我们可以枚举所有的取点的可能,然后求得最小的ratio,然后由于n只有15,所以我们可以用1~(1 << n)来表示所有取点的可能,对应的二进制数如果为1表示这个点选取,0表示不选取。然后看一下当前这个状态中选取的点是否等于m,是的话就表示是一种可能。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <queue>
#include <set>
#include <map>
#include <algorithm>
#include <math.h>
#include <vector>
using namespace std;
const int maxn=20;//点的数目
int a[1<<16];//用来保存对应二进制数所对应的点的权值的和
int w[maxn*maxn];
int p[maxn];//每个点的权值
int fa[maxn];//并查集用的
int dis[maxn][maxn];//点与点之间的距离
vector<int>g[1<<16];//用来保存对应状态的点
int n,m;
int find(int x){//并查集
return fa[x]==x?x:fa[x]=find(fa[x]);
}
struct node {
int u,v,w;
bool operator <(const node &t)const {
return w<t.w;
}
}edge[maxn*maxn];
int kurskal(int x){//x表示x所对应的状态所对应的最小生成树 ,用kurskal
int cnt=0;
for (int i=0;i<=n;i++){//初始化
fa[i]=i;
}
for (int i=0;i<g[x].size();i++){
int u=g[x][i];
for (int j=i+1;j<g[x].size();j++){
int v=g[x][j];
if (i==j)continue;
edge[cnt++]=(node){u,v,dis[u][v]};
}
}
sort(edge,edge+cnt);
int sum=0;
int ans=0;
for (int i=0;i<cnt;i++){
int fu=find(edge[i].u);
int fv=find(edge[i].v);
if (fu!=fv){
fa[fu]=fv;
sum++;
ans+=edge[i].w;
}
if (sum==m-1)break;
}
return ans;
}
int main()
{
while (scanf ("%d%d",&n,&m)&&n&&m){
for (int i=0;i<n;i++){
scanf ("%d",&p[i]);//点的权值
}
for (int i=0;i<(1<<16);i++)g[i].clear();//初始化
for (int i=0;i<n;i++){
for (int j=0;j<n;j++){
scanf ("%d",&dis[i][j]);//边的权值
}
}
memset (a,0,sizeof (a));//初始化
for (int i=0;i<(1<<n);i++){
int cnt=0;//计算i这个状态所对应的点的数目
int tmp=0;//保存所有点的权值之和
for (int j=0;j<n;j++){
if (i&(1<<j)){
cnt++;
tmp+=p[j];
g[i].push_back(j);//保存状态的点
}
}
if (cnt==m)a[i]=tmp;
}
int ans;
double tmp_ans=10000000000;
for (int i=0;i<(1<<n);i++){//遍历所有的点,得到最小值所对应的状态
if (a[i]){
double tmp=1.0*kurskal(i)/a[i];
if (tmp<tmp_ans){
tmp_ans=tmp;
ans=i;
}
}
}
sort(g[ans].begin(),g[ans].end());//排序
for (int i=0;i<g[ans].size();i++){//输出结果
if (i==0)printf ("%d",g[ans][i]+1);
else printf (" %d",g[ans][i]+1);
}
printf ("\n");
}
return 0;
}