题意:大意就是有边权,有点权,求选定m个点,生成一颗树,使k=选定边权/点权 最小。
思路:由k=边权/点权,选m个点时,点权已经固定了,所以要使这些点的边权和最小,自然为最小生成树。枚举+kruskal 枚举由于每个点可以选和不选,而且最多也就20个点,所以直接上2进制,1表示选,0表示不选这个点,然后用这些点来建树。
代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 40,inf = 0x3f3f3f3f;
struct node
{
int from,to,w;
node(){}
node(int a,int b,int c)
{from = a; to = b; w = c;}
}edge[maxn*maxn];
int value[maxn],data[maxn][maxn];
int n,m,edgenum;
bool cmp(node a,node b)
{ return a.w < b.w; }
int f[maxn],vis[maxn];
int getf(int i)
{ return i==f[i]?i:f[i]=getf(f[i]); }
double kruskal(int sta)
{
for(int i = 0; i < maxn; i++) f[i] = i,vis[i] = 0;
int vw = 0,th = 1,num = 0,ew = 0;
while(sta){if(sta&1)vis[th]=1; th++;sta=(sta>>1);}
for(int i = 0; i < edgenum; i++)
{
int u = edge[i].from,v = edge[i].to;
if(!vis[u]|| !vis[v]) continue;
int x = getf(u),y = getf(v);
if(x!=y)
{
f[x] = y;
num++;
ew += edge[i].w;
if(num == m-1) break;
}
}
for(int i = 1; i <= n; i++)
if(vis[i]) vw += value[i];
if(num != m-1) return inf;
else return (double)ew/vw;
}
int main()
{
while(~scanf("%d%d",&n,&m) && n+m)
{
for(int i = 1; i <= n; i++)
scanf("%d",&value[i]);
edgenum = 0;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
{
scanf("%d",&data[i][j]);
if(i!=j) edge[edgenum++] = node(i,j,data[i][j]);
}
sort(edge,edge+edgenum,cmp);
double ansa = inf;
int anssta = 0;
for(int i = 1; i < (1<<n) ; i++)
{
int k = i,ti = 0;
while(k){if(k&1) ti++; k = (k>>1);}
if(ti!=m)continue;
else
{
double w = kruskal(i);
if(w < ansa) anssta = i,ansa = w;
}
}
int out = 1,flag = 0;
while(anssta)
{
if(anssta&1)
{
flag?printf(" %d",out):printf("%d",out);
flag = 1;
}
out++;
anssta = (anssta >> 1);
}
printf("\n");
}
return 0;
}