魔法森林

题目:

题目描述
亮亮在梦中游历了魔法城堡后,对此心驰神往,于是用自己制造的法杖,创造了一片魔法森林。

这片森林中一开始有 n 个节点,没有边相连,若想要在第 i 个点和第 j 个点之间建立一条双向通路,则需花费 Cij 的魔法值。

每个结点上住着一个魔法居民,若两个节点间有边直接相连,则他们就成为了邻居。居民一共有三种类型:

①村民:他们只能通过道路拜访自己的邻居。 ②巫师:他们可以拜访自己的邻居以及邻居的邻居。
③大魔法师:由于他们拥有法力,因此可以拜访所有与自己连通的人。

亮亮不希望有人孤单,因此他保证了每种类型的居民要么不出现,否则至少出现两个。同时,他又希望大家能建立良好的关系,所以他决定花费魔法值为魔 法森林修路,使得任意居民都可以拜访其他所有的居民。

他想知道,最少需要建立多少条道路才能达成自己的心愿。在道路数目最少的前提下,花费的魔法值最小又是多少。

输入输出格式
输入格式:
第一行有一个整数 n。

第二行有 n 个整数,第 i 个整数表示 i 号节点所居住的居民的类型(1 表示 村民,2 表示巫师,3 表示大魔法师)。

接下来 n 行,每行 n 个数,是一个 n*n 的矩阵 Cij。数据保证Cij = Cji, Cii=0。

输出格式:
一行两个整数,分别表示最小道路数和最小魔法值。

输入输出样例
输入样例#13 
1 1 1
0 1 2
1 0 3
2 3 0
输出样例#13 6
说明
对于30%的数据,只存在大魔法师;

对于100%的数据,1 <= n <= 2500 <= Cij <= 10^9

思路:
分类讨论——

  1. 只有③,此时求最小生成树就好。
  2. 存在①,那么只需要满足①,这张图就会连通而满足③,且每两组②都会与至少一组①相连。
  3. 存在②,不存在①。
    · 只有两个②,这种情况考试时没注意到,分两种情况考虑,一是这两个②直接相 连,其他的③选择路径较小的②相连,二是和下面的情况相同的考虑方法。
    · 不只两个②,那么枚举每个节点作为中心构造菊花图就好。

代码:

#include<bits/stdc++.h>
using namespace std;

#define read(x) scanf("%lld",&x)
#define maxn 250
#define maxm 250*250
#define ll long long

struct Edge {
	ll x,y,z;
	Edge(){}
	Edge(ll xx,ll yy,ll zz) {
		x=xx,y=yy,z=zz;
	}
	bool operator < (const Edge& oth) const {
		return z<oth.z;
	}
};

ll n;
ll w[maxn+5];
ll c[maxn+5][maxn+5];

ll fa[maxm+5];
vector<Edge> e;

bool bld[maxn+5][maxn+5];

ll find(ll x) {
	if(fa[x]) return fa[x]=find(fa[x]);
	return x;
}

ll kruskal() {
	ll s=0;
	for(ll i=1;i<=n;i++) for(ll j=i+1;j<=n;j++) e.push_back(Edge(i,j,c[i][j]));
	sort(e.begin(),e.end());
	for(ll i=0;i<e.size();i++) {
		ll fa1=find(e[i].x),fa2=find(e[i].y);
		if(fa1==fa2) continue;
		fa[fa1]=fa2;
		s+=e[i].z;
	}
	return s;
}

int main() {
	read(n);
	for(ll i=1;i<=n;i++) read(w[i]);
	for(ll i=1;i<=n;i++) for(ll j=1;j<=n;j++) read(c[i][j]);
	
	for(ll i=1;i<=n;i++) if(w[i]!=3) goto NOTONLY;
	printf("%lld %lld",n-1,kruskal());
	
	return 0;	
	NOTONLY:;
	
	bool flg=0;
	ll ans=0,s=0;
	int w2=0;
	int id1=0,id2=0;
	
	for(ll i=1;i<=n;i++)
		if(w[i]==1) {
			flg=true;
			for(ll j=1;j<=n;j++) {
				if(i!=j&&!bld[i][j]&&!bld[j][i]) {
					bld[i][j]=true;
					ans+=c[i][j];
					s++;
				}
			}
		} else if(w[i]==2) {
			w2++;
			if(id1) id2=i;
			else id1=i;
		}
	if(flg) {
		printf("%lld %lld",s,ans);
		return 0;
	}
	
	if(w2==2) {
		ll sum=0;
		sum+=c[id1][id2];
		for(int i=1;i<=n;i++) {
			if(w[i]==3) {
				sum+=min(c[i][id1],c[i][id2]);
			}
		}
		s=sum;
	}else s=1e16;
	for(ll i=1;i<=n;i++) {
		ll sum=0;
		for(ll j=1;j<=n;j++) sum+=c[i][j];
		if(sum<s) s=sum,ans=i;
	}
	
	printf("%lld %lld",n-1,s);
	
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值