该题就是最小生成树算法的变形,由于这个比值没有什么规律,不可能一下子算出最小情况,我们可以很容易发现,结点数非常少,所以我们可以枚举出m个结点的所有组合,这样,结点权值只和就确定了,为了使得比值最小,那么就要使得边权值之和最小,也就是最小生成树模板了。
枚举n个数中的m个可以有两种方法: dfs和二进制枚举子集。
该题我用的二进制,感觉比较方便。
细节参见代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<cmath>
#include<set>
using namespace std;
typedef long long ll;
const double INF = 1000000000;
const int maxn = 500 + 5;
int n,q,T,x,y,cnt,kase = 0,m,a,b,p[maxn],node[maxn];
double dx[maxn],dy[maxn];
int setfind(int x) {
return p[x] == x ? x : p[x] = setfind(p[x]);
}
struct Edge{
int a,b,c;
bool operator < (const Edge& rhs) const {
return c < rhs.c;
}
}e[maxn*maxn];
void solve() {
sort(e,e+cnt);
double ans = INF;
vector<int> res;
for(int i=1;i<(1<<n);++i) {
int c = 0;
for(int j=0;j<n;++j)
if(i & (1<<j)) ++c;
if(c == m) {
int vis[20] = {0};//标记那些结点在当前集合中
double cur = 1,cur1=0,cur2=0;
for(int j=0;j<n;++j)
if(i & (1<<j)) {
vis[j+1] = 1; cur1 += node[j];
}
for(int j=1;j<=n;j++) p[j] = j;
for(int j=0;j<cnt;j++) { //最小生成树
int x = e[j].a, y = e[j].b;
if(!vis[x]||!vis[y]) continue;
x = setfind(e[j].a); y = setfind(e[j].b);
if(x != y) {
cur++;
cur2 += e[j].c;
p[x] = y;
}
if(cur == m) break;
}
if(abs(ans-cur2/cur1)<1e-6) { //更新答案
int cc = 0;
bool ok = false;
for(int j=1;j<=n;j++) if(vis[j]) {
if(j < res[cc]) { ok = true; break; }
else if(j > res[cc]) break;
else cc++;
}
if(ok) {
res.clear();
for(int j=1;j<=n;j++) if(vis[j])
res.push_back(j);
}
}
else if(ans > cur2/cur1) {
ans = cur2/cur1; res.clear();
for(int j=1;j<=n;j++) if(vis[j])
res.push_back(j);
}
}
}
for(int i=0;i<m;i++) { //打印解
if(i==0) printf("%d",res[i]);
else printf(" %d",res[i]);
}
printf("\n");
}
int main() {
while(~scanf("%d%d",&n,&m)) {
if(!n && !m) break;
for(int i=0;i<n;i++) scanf("%d",&node[i]);
cnt = 0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
scanf("%d",&a);
if(i == j) continue;
e[cnt].a = i; e[cnt].b = j; e[cnt++].c = a;
}
}
solve();
}
return 0;
}