题意: 给n个点及各点间的距离,从中选出m个点 ,使得 边权的和 / 点权的和 最小。
思路: 从n个点中选出m个点(枚举每种可能的情况),计算出它的。。。最小值。所以可以用dfs枚举出各种情况,再用prime算出每种情况的。。。最小值。
#include<iostream>
#include<string.h>
#define INF 999999
using namespace std;
int map[20][20],dis[20];
int vis[20],mark[20],nx[20] , flag[20];
int bian,dian;
int n,m;
int prime(int s , int temp[20][20])
{
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
int sum = 0;
int min, rec;
for(int i=1;i<=n;i++) dis[i] = temp[s][i];
vis[s]=1;
for(int i=1;i<n;i++)
{
min = INF;
for(int j=1;j<=n;j++)
{
if(vis[j]==0 && dis[j]< min)
{
min = dis[j];
rec = j;
}
}
vis[rec]=1;
sum+=min;
if(i==m-1) return sum; //因为每次dis会更新,且只需找m-1条边的最小生成树。
for(int j=1;j<=n;j++)
if(dis[j]> temp[rec][j]) dis[j]=temp[rec][j];
}
}
void dfs(int g[20] , int t , int pre)
{
if(t==m) //挑选的点达到m ,可计算其。。。最小值
{
int sum = 0 , s;
for(int i=1;i<=n;i++) if(g[i]==1) sum+=nx[i]; //点权和
int temp[20][20]; //用temp记录这挑选出的m个点的边的关系
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(g[i]==0 || g[j]==0)
temp[i][j] = temp[j][i] = INF;
else
{
s = i;
temp[i][j]=map[i][j];
temp[j][i]=map[j][i];
}
}
}
int cnt = prime(s ,temp);
if((cnt*dian - sum*bian)<0) //与之前的最小值比较
{
bian = cnt;
dian = sum;
for(int i=1;i<=n;i++) mark[i] = g[i];
}
}
else
{
for(int i=pre ; i<=n;i++) //枚举,各个点都有可能出现或不出现在m个点中
{
if(g[i]==0)
{
g[i]=1;
dfs(g,t+1 , i);
g[i]=0;
}
}
}
}
int main()
{
while(scanf("%d%d",&n,&m)!=EOF && (m+n))
{
memset(map,0,sizeof(map));
memset(mark,0,sizeof(mark));
memset(flag,0,sizeof(flag));
bian = INF; dian = 1;
for(int i=1;i<=n;i++) scanf("%d",&nx[i]);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)
scanf("%d",&map[i][j]);
dfs(flag, 0 , 1);
int count=0;
for(int i=1;i<=n;i++)
{
if(mark[i]==1)
{
printf("%d",i);
count++;
if(count!=m) printf(" ");
}
}printf("\n");
}
return 0;
}