[NOIp2017 TG Day2 T2] 宝藏

6 篇文章 0 订阅
5 篇文章 0 订阅

原题链接:https://www.luogu.org/problemnew/show/P3959

宝藏

题目描述

参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋, 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。

新开发一条道路的代价是:

L×K

L代表这条道路的长度,K代表从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的 宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋) 。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代 价最小,并输出这个最小值。

输入输出格式
输入格式:

第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。

接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏 屋的编号(编号为 1~n),和这条道路的长度 v。

输出格式:

输出共一行,一个正整数,表示最小的总代价。

输入输出样例
输入样例#1:

4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 1

输出样例#1:

4

输入样例#2:

4 5
1 2 1
1 3 3
1 4 1
2 3 4
3 4 2

输出样例#2:

5

说明
【样例解释1】

小明选定让赞助商打通了1号宝藏屋。小明开发了道路 1→2 ,挖掘了 2 号宝藏。开发了道路 1→4 ,挖掘了4号宝藏。还开发了道路4→3,挖掘了3号宝藏。工程总代价为:1×1+1×1+1×2=4

【样例解释2】

小明选定让赞助商打通了 1 号宝藏屋。小明开发了道路 1→2 ,挖掘了 2 号宝 藏。开发了道路 1→3 ,挖掘了 3 号宝藏。还开发了道路 1→4 ,挖掘了 4 号宝 藏。工程总代价为:1×1+3×1+1×1=5

【数据规模与约定】

对于 20%的数据: 保证输入是一棵树,1≤n≤8 ,v≤5000 且所有的 v 都相等。

对于 40%的数据:1≤n≤8 ,0≤m≤1000 ,v≤5000 且所有的 v 都相等。

对于 70%的数据:1≤n≤8 ,0≤m≤1000 ,v≤5000

对于 100%的数据:1≤n≤12 ,0≤m≤1000 ,v≤500000

题解

博主NOip打了一发Prim成功get15分。。。orz我是真的弱。

为了解心头之恨博主励志用Prim把这玩意儿A掉,但是Prim实际上是一种贪心,不是特别适用这道题,所以我们给贪心升个级,经观察,此题数据量极少。。。

然后就有了——模拟退火!!!

是的,当我们Prim的时候,我们可以跳过堆顶的一些边,以一定的几率接受一个不优的边,从而跳出局部最优解,跟退火有异曲同工之妙啊!我们只需要退个250次就A了!!!

代码
#include<bits/stdc++.h>
using namespace std;
const int M=15;
struct sd{int f,t;};
int n,m,mmp[M][M],dep[M],vis[M];
bool operator <(sd a,sd b)
{return dep[a.f]*mmp[a.f][a.t]>dep[b.f]*mmp[b.f][b.t];}
priority_queue<sd>dui;
sd used[1005];
void in()
{
	memset(mmp,63,sizeof(mmp));
	int a,b,c;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	scanf("%d%d%d",&a,&b,&c),mmp[a][b]=mmp[b][a]=min(mmp[a][b],c);
}
int sa(int root)
{
	memset(dep,0,sizeof(dep));
	memset(vis,0,sizeof(vis));
	memset(used,0,sizeof(used));
	while(!dui.empty())dui.pop();
	int p=0,ans=0;sd e;
	vis[root]=dep[root]=1;
	for(int i=1;i<=n;++i)
	if(mmp[root][i]<mmp[0][0])
	dui.push((sd){root,i});
	for(int i=1;i<n;++i)
	{
		e=dui.top();dui.pop();
		while(!dui.empty()&&(vis[e.t]||rand()%n<1))
		{
			if(!vis[e.t])used[++p]=e;
			e=dui.top();dui.pop();
		}
		vis[e.t]=1;dep[e.t]=dep[e.f]+1;
		while(p>0)
		dui.push(used[p--]);
		for(int i=1;i<=n;++i)
		if(!vis[i]&&mmp[e.t][i]<mmp[0][0])
		dui.push((sd){e.t,i});
		ans+=mmp[e.f][e.t]*dep[e.f];
	}
	return ans;
}
void ac()
{
	int ans=1e9;
	for(int i=1;i<=250;++i)
	for(int j=1;j<=n;++j)
	ans=min(ans,sa(j));
	printf("%d",ans);
}
int main()
{
	srand(19260817);
	in();ac();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值