【洛谷P3959】宝藏

题目

题目链接:https://www.luogu.com.cn/problem/P3959
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 \(n\) 个深埋在地下的宝藏屋, 也给出了这 \(n\) 个宝藏屋之间可供开发的$ m$ 条道路和它们的长度。

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

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

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

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

\[\mathrm{L} \times \mathrm{K} \]

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

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

思路

\(n\) 特别小,有效的边数 \(m\) 也不超过 \(70\),要求最优解。
直接上模拟退火,每次在当前最优的序列上随机两个位置交换,\(O(n^2)\) 贪心求路径长度,再降温即可。

代码

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

const int N=15,M=70;
const double delta=0.996;
int n,m,tot,head[N],a[N],dep[N],dis[N][N];
ll ans;

ll work()
{
	memset(dep,0x3f3f3f3f,sizeof(dep));
	dep[a[1]]=0;
	ll sum=0;
	for (int i=2;i<=n;i++)
	{
		ll mind=100000000000000000,pos;
		for (int j=1;j<=n;j++)
			if (1LL*(dep[j]+1)*dis[a[i]][j]<mind)
				mind=1LL*(dep[j]+1)*dis[a[i]][j],pos=j;
		sum+=mind; dep[a[i]]=dep[pos]+1;
	}
	return sum;
}

void solve()
{
	srand(rand());
	double t0=8000,t1=1e-14;
	while (t0>t1)
	{
		int x=rand()%n+1,y=rand()%n+1;
		swap(a[x],a[y]);
		ll sum=work();
		if (sum<ans) ans=sum;
		else if (exp(sum-ans)<1.0*rand()/RAND_MAX) swap(a[x],a[y]);
		t0*=delta;
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	memset(dis,0x3f3f3f3f,sizeof(dis));
	scanf("%d%d",&n,&m);
	srand(114514+19260817);
	for (int i=1,x,y,z;i<=m;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		dis[x][y]=dis[y][x]=min(dis[x][y],z);
	}
	for (int i=1;i<=n;i++) a[i]=i;
	random_shuffle(a+1,a+1+n);
	ans=1000000000000000;
	solve(); solve(); solve();
	printf("%lld",ans);
	return 0;
}
posted @ 2020-10-20 10:29  stoorz  阅读(148)  评论(0编辑  收藏  举报