题目链接:
题目大意:
为了以防禽流感,国家要在村庄中建一些卫生站,村庄是编号从0到n-1,对于每个村庄i,要么直接在该村庄新建卫生站,要么连接该村庄到它最近的一个医疗中(这种花费就是两者之间的距离),连接可以想成是派专车接送什么的。。
现在给出在每个村庄新建的费用,以及村庄之间的连接情况(保证是一棵树)。
要使所有村庄都能访问卫生站的最小花费。
总花费也就是,在树上被选上点对应的值之和 + 未选中点到最近选中点的距离之和,怎么选能使总花费最小。
思路:
这题的标准思路应该是树形dp。
但我却从网上一位大神处看到,贪心+搜索剪枝竟然也能过。。
大概思路是,先按照在每个点新建卫生站的花费排序,然后从小到大枚举,每个点处,用一个数组维护每个点到最近的卫生站的距离d[],以及当前所有选中点的花费之和sum,然后按照当前点选或者不选继续递归下去,剪枝总共有两个,在程序中以说明。。
代码:
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
using namespace std;
#define maxn 50
#define inf 0x3fffffff
int n,p;
int dis[maxn][maxn];
int ans,num;
struct Node{
int id;
int val;
}node[maxn];
bool cmp(Node a,Node b){
return a.val < b.val;
}
void flyod(){
for(int k=0;k<n;k++){
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
dis[i][j] = min(dis[i][j],dis[i][k] + dis[k][j]);
}
}
}
/* for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
printf("%d %d %d\n",i,j,dis[i][j]);
}
} */
}
void dfs(int k,int cnt,int sum,int d[]){
if(k==n || cnt==p) return;
int temp = sum + node[k].val;
if(temp>ans) return; // 剪枝1: 如果当前的已选择点的和已经大于ans,还没有加上没选的点的花费,这种后面状态肯定都大于ans
int tm[maxn];
for(int i=0;i<n;i++){
tm[i] = min(d[i],dis[node[k].id][i]);
temp += tm[i];
}
if(temp<ans) ans = temp;
if(++num > 1000000) return; // 贪心剪枝: 因为是按照结点花费排序,一定次数之后,后面肯定都是比较大的(这真是神技!!)
dfs(k+1,cnt+1,sum+node[k].val,tm);
dfs(k+1,cnt,sum,d);
}
int main(){
while(~scanf("%d%d",&n,&p)){
for(int i=0;i<n;i++) {
scanf("%d",&node[i].val);
node[i].id = i;
}
sort(node,node+n,cmp);
memset(dis,0x3f,sizeof(dis));
int a,b,t;
for(int i=0;i<n;i++) dis[i][i] = 0;
for(int i=0;i<n-1;i++){
scanf("%d %d %d",&a,&b,&t);
dis[a][b] = dis[b][a] = t;
}
flyod();
ans = inf, num=0;
int d[maxn];
for(int i=0;i<n;i++) d[i] = inf;
dfs(0,0,0,d);
printf("%d\n",ans);
}
return 0;
}