题目
题目链接:https://www.luogu.com.cn/problem/P3959
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 (n) 个深埋在地下的宝藏屋, 也给出了这 (n) 个宝藏屋之间可供开发的$ m$ 条道路和它们的长度。
小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远, 也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路 则相对容易很多。
小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某 个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。
在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以 任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路 所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏 屋之间的道路无需再开发。
新开发一条道路的代价是:
[mathrm{L} imes 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;
}