第八届省赛F题 (枚举+最小生成树)

Description

A kingdom has n cities numbered 1 to n, and some bidirectional roads connecting cities. The capital is always city 1.
After a war, all the roads of the kingdom are destroyed. The king wants to rebuild some of the roads to connect the cities, but unfortunately, the kingdom is running out of money. The total cost of rebuilding roads should not exceed K.
Given the list of m roads that can be rebuilt (other roads are severely damaged and cannot be rebuilt), the king decided to maximize the total population in the capital and all other cities that are connected (directly or indirectly) with the capital (we call it "accessible population"), can you help him?

Input

The first line of input contains a single integer T (T<=20), the number of test cases. 
Each test case begins with three integers n(4<=n<=16), m(1<=m<=100) and K(1<=K<=100,000). 
The second line contains n positive integers pi (1<=pi<=10,000), the population of each city. 
Each of the following m lines contains three positive integers u, v, c (1<=u,v<=n, 1<=c<=1000), representing a destroyed road connecting city u and v, whose rebuilding cost is c. 
Note that two cities can be directly connected by more than one road, but a road cannot directly connect a city and itself.

Output

For each test case, print the maximal accessible population.

Sample Input

2
4 6 6
500 400 300 200
1 2 4
1 3 3
1 4 2
4 3 5
2 4 6
3 2 7
4 6 5
500 400 300 200
1 2 4
1 3 3
1 4 2
4 3 5
2 4 6
3 2 7 

Sample Output

1100
1000 
题意 :  n个城市,每个城市有人口数a[i]  , 1~n   ,首都是城市1  ; 给m条城市之间的道路 ,问选择修哪些路 ,使得一些城市能直接或者间接地能够到达首都(不需要所有点都联通首都) ,同时使得这些城市的总人数最大;    每条路修建要花费c  ,   修的道路总花费不能超过K  ; 输出最后最大的总人数
要求最终修的路让哪些城市与首都联通了 ,   枚举点[1,n]的子集 ,  如果枚举的这些点可以联通在一起的,
(即他们构成的图不包含其他点) , 并且修的花费总和不超过K ,则好似一组可行解, 不停更新最大的可行解即可;
假设枚举了w个点 ,和这些点有关的边有m条 ,那么求一个最小生成树, 最小生成树<=K ,说明有解 如果最小生成树都>K,说明这些点不能联通起来 .
注意两点间边有多条 !!
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int inf = 1<<30 ;

int n,m,k,tmp;
int a[100],g[50][50],dist[100],vis[100],s[100];


int work1()  //普利姆 
{
	int now ,pos=0,cost=0,MIN=inf,num;
	  for(int i = 0 ; i < tmp; i++ )
	  {
	  	      now =s[i] ;   
			  dist[now] = g[1][now] ;
	  	      vis[now]=0;	  	 
	  } 
	vis[1]=1;num=0;
	for(int i = 1 ; i < tmp ; i++)
	{
		  MIN=inf ;
		   for(int j = 0 ; j < tmp;j++)
		   {
		   	      now = s[j] ;
		   	      if(MIN > dist[now] && !vis[now]) 
		   	      {
		   	      	    MIN=dist[now] ;
		   	      	    pos=now ;
		   	      }
		   } 
		    cost += MIN ;  
		    vis[pos]=1;
		    for(int j = 0 ; j < tmp; j++)
		    {
		    	   now= s[j] ;
		    	   if(dist[now] > g[pos][now] && !vis[now])
		    	       dist[now] = g[pos][now] ; 
		    }
	}
	int sum = 0;
	for(int i = 0 ; i < tmp ; i++)
	 {   now = s[i] ;
	     sum += a[now] ;
	     if(vis[now]) num++;  //记录这些点是否都被标记 
    }  
    if(cost <= k && num == tmp)  //总花费小于等K,并且图要联通 
	 return sum ;
	else  return 0 ; 
}

int  print_subset(int x)
{
	 tmp =0 ;
  for(int i=0;i<n;i++)
   if(x&(1<<i))
   {
   	    s[tmp++]=i+1;
   	    
   }
   if(s[0]!=1 )  //子集没有首都 
     return 0;
   else return 1;  
}

int main()
{
	   int t ,u,v,w;
	   scanf("%d",&t) ;
	   while(t--)
	   {
	   	       scanf("%d%d%d",&n,&m,&k);
	   	       for(int i = 1 ; i <= n ; i++)
	   	         for(int j = 1 ; j <= n ;j++)
	   	           g[i][j]=inf ;
	   	       for(int i = 1 ; i <= n ; i++)
				    scanf("%d",&a[i]) ;   
				 
	   	       for(int i = 1 ; i <= m ; i++)
	   	       {
	   	       	     scanf("%d%d%d",&u,&v,&w) ;
	   	       	     if(g[u][v]>w)
	   	       	     g[u][v]=g[v][u]=w ;          
			  }
			  int ans = 0 ;
	   	          
	   	        for(int i=0;i<(1<<n);i++)  //二进制枚举子集 
                {
                   int flag = print_subset(i);  //当前枚举的子集是否有首都 
	   	         	if(tmp==1)  //当前子集就只有首都 
					{	
					  ans = max(ans,a[1]) ;
					  continue; 
					} 
					  int ans1 ;
					  if(flag )  //有首都就做最小生成树 ; 
	   	         	  {  
						  ans1 = work1() ;
						  if(ans1 > ans)
						  {
						  	  ans = ans1; 
						  }
					     
	   	         	  } 
				      else  continue ;
					  
	   	         }
	   	     
	   	      printf("%d\n",ans) ;
	   } 
	return 0;
} 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
普里姆算法是一种基于贪心思想的论算法,用于求解一个带权无向最小生成树。 以下是用C语言实现普里姆算法求解最小生成树的基本步骤: 1. 定义一个结构体来表示边,包含边的起点、终点和边权值。 ``` struct Edge { int v, w; // 终点和边权值 }; ``` 2. 定义一个小根堆,存储当前已经加入最小生成树的节点的所有出边。 ``` typedef struct { int v, w; // 节点编号和边权值 } HeapNode; HeapNode heap[MAXN]; // 小根堆数组,用于存储当前已经加入最小生成树的节点的所有出边 int size = 0; // 小根堆的大小 void push(int v, int w) { // 向小根堆加入一个节点的出边 heap[size].v = v; heap[size].w = w; size++; int i = size - 1; while (i > 0) { int p = (i - 1) / 2; if (heap[p].w > heap[i].w) { HeapNode t = heap[p]; heap[p] = heap[i]; heap[i] = t; i = p; } else { break; } } } HeapNode pop() { // 从小根堆弹出权值最小的边 HeapNode res = heap[0]; heap[0] = heap[size - 1]; size--; int i = 0; while (i * 2 + 1 < size) { int l = i * 2 + 1; int r = i * 2 + 2; int j = l; if (r < size && heap[l].w > heap[r].w) { j = r; } if (heap[i].w > heap[j].w) { HeapNode t = heap[i]; heap[i] = heap[j]; heap[j] = t; i = j; } else { break; } } return res; } ``` 3. 定义一个数组dist,用于存储每个节点距离最小生成树的距离。 ``` int dist[MAXN]; // dist[i]表示节点i距离最小生成树的距离 ``` 4. 定义一个函数,用于求解最小生成树。 ``` void prim(int n, int m, Edge edges[][MAXN], int s) { // n为节点数,m为边数,edges为邻接矩阵,s为起点 int ans = 0; // 最小生成树的权值和 memset(dist, 0x3f, sizeof(dist)); // 初始化dist数组为无穷大 dist[s] = 0; // 起点到自己的距离为0 push(s, 0); // 将起点加入小根堆 while (size > 0) { // 当小根堆不为空时 HeapNode node = pop(); // 弹出权值最小的边 int v = node.v, w = node.w; if (dist[v] < w) { // 如果当前节点已经加入最小生成树,则跳过 continue; } ans += w; // 计算最小生成树的权值和 dist[v] = 0; // 将当前节点加入最小生成树 for (int i = 1; i <= n; i++) { // 枚举当前节点的所有出边 if (edges[v][i].w < INF && dist[i] > edges[v][i].w) { // 如果当前节点到i的距离比已知的距离更小,则将i加入小根堆 dist[i] = edges[v][i].w; push(i, dist[i]); } } } printf("%d\n", ans); // 输出最小生成树的权值和 } ``` 完整代码如下:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值