编程之美初赛第二场 攻城略地(贪心,图论)

题意:A、B两国间发生战争。已知A国共有n个城市(编号1, 2, …, n),城市间有一些道路相连。每座城市的防御力为w,直接攻下该城的代价是w。若该城市的相邻城市(有道路连接)中有一个已被占领,则攻下该城市的代价为0。除了占领城市,B国还要摧毁A国的交通系统,因而他们需要破坏至少k条道路。由于道路损毁,攻下所有城市的代价相应会增加。假设B国可以任意选择要摧毁的道路,那么攻下所有城市的最小代价是多少?

输入:第一行一个整数T,表示数据组数,以下是T组数据。每组数据第一行包含3个整数n, m, k。第二行是n个整数,分别表示占领城市1, 2, …, n的代价w。接下来m行每行两个数i, j,表示城市i与城市j间有一条道路。

思路:实际上是贪心的想法。首先考虑原图(不删除任何边),其最小代价是每个连通分支中的最小代价之和。下面考虑删边,如果可以继续删边而不增加连通分支数量,那么最小代价不会增加。直到将原图删成了一棵森林。如果还需要继续删边,那么必然会增加代价,而显然增加的量为没有计入总代价的代价最小点的代价(设为t点)。因为总可以在t所在的连通分支中将t与之前此连通分支内的最小代价点分开。如此删除直到删除符合题意的边为止。

一开始大数据没有过,因为我在读入数据后先排序,然后每找到一个连通分支的最小代价就标记相应位置的数已经选取。实际上这样的话如果原始的图没有边,那么复杂度变成了O(n^2),所以会TLE。实际上在深搜判连通的时候再产生t数组,最后排序扫一遍即可,复杂度O(nlogn)。(转载)

http://hihocoder.com/problemset/problem/1160

#include<iostream>  
#include<algorithm>  
#include<string>  
#include<map>  
#include<vector>  
#include<cmath>  
#include<string.h>  
#include<stdlib.h>  
#include<cstdio>  
#define ll long long  
using namespace std;
vector<int> g[1000001];
int vis[1000001];
int x[1000001];
int f[1000001];
int cnt,w,c,u;
void dfs(int p){
	if(vis[p]==1)
		return;
	vis[p]=1;
	if(w>x[p]){
		if(w!=100000000) //为了不把连通分支中最小的一个值导入 
			f[++c]=w;
		w=x[p];
	}
	else
		f[++c]=x[p];
	for(int i=0;i<g[p].size();++i)
		dfs(g[p][i]);
}
int main(){
	int t;
	scanf("%d",&t);  
	u=0;
	while(t--){
		int n,m,k;
		scanf("%d %d %d",&n,&m,&k);  
		for(int i=0;i<n;++i){
			scanf("%d",&x[i]);  
			g[i].clear();
		}
		int a,b;
		for(int i=0;i<m;++i){
			scanf("%d %d",&a,&b);  
			a--;
			b--;
			g[a].push_back(b);
			g[b].push_back(a);
		}
		memset(vis,0,sizeof(vis));
		cnt=0;w=0;
		ll s=0;
		c=-1;
		for(int i=0;i<n;++i){
			if(vis[i]==0){
				w=100000000;
				dfs(i);
				s+=w;     //w求得为每个连通分支的最小代价  
				cnt++;    //连通分支数  
			}
		}
		sort(f,f+c+1);
		int ss=k-(m-(n-cnt));  //n-cnt:原图为森林时的边数  m-(n-cnt):将原图变成森林需要删的边数 
		for(int i=0,j=ss;i<=c,j>0;++i,j--){
			s+=f[i];
		}	
		 printf("Case #%d: %lld\n",++u,s);  
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值