NOIP2017 宝藏

题目传送门

感觉这个题被玩坏了……
翻看了一下题解区,发现了(N)种神仙做法:

  1. 随机化搜索(伪模拟退火)
  2. 搜索+剪枝
  3. 模拟退火
  4. 不对但是能A的状压(DP)
  5. 正确的状压(DP)
    其中(DP)又分用(DFS)(DP),不用(DFS)(DP),而且各种(DP)的时间复杂度也各不相同……

我还有什么话可说呢?


学习科学,使用玄学
——zhx

既然标签上都写了搜索和随机化了,那我们肯定要用第一种神仙做法(其实是因为我看不懂状压DP)

这里的随机化用到了一点模拟退火的思想,以一定的概率去接受新的连边方式,这个概率是随着(n)的上升而增大的
为了寻找最优解,我们可以让它多跑几遍,取最小值

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define inf 1000000
using namespace std;
int g[15][15],deth[15];
struct zzz{
	int f,t;
}pass[1010]; int top;
bool operator < (struct zzz a,struct zzz b){
	return deth[a.f]*g[a.f][a.t] > deth[b.f]*g[b.f][b.t];
}
bool vis[15];
int n,m;
int bfs(int s){
	priority_queue <zzz> q;
	int cost=0;
	//将一开始的边入堆
	for(int i=1;i<=n;i++)
	  if(g[s][i]<inf)
	    q.push(zzz{s,i});
	//因为要多加(n-1)条边,所以循环到n-1
	for(int i=1;i<n;i++){
		zzz e=q.top(); q.pop();
		while(!q.empty()&&(vis[e.t]||(rand()%n<1))){ //随机找边
			if(!vis[e.t]) pass[++top]=e;  //跳过的边仍然可能用到,先将它们存起来
			e=q.top(); q.pop();  //随机一条新边
		}
		vis[e.t]=1; deth[e.t]=deth[e.f]+1;
		for(int i=1;i<=top;i++)  //将之前跳过的边在怼进堆里
		  q.push(pass[i]);
		top=0;
		for(int i=1;i<=n;i++)
		  if(g[e.t][i]<inf&&!vis[i])  //扩展新的边
			q.push(zzz{e.t,i});
		cost+=g[e.f][e.t]*deth[e.f];
	}
	return cost;
}
int read(){
	int k=0; char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	  k=k*10+c-48, c=getchar();
	return k;
}
int main(){
	srand(19****17); //打码保平安
	memset(g,127/3,sizeof(g));
	n=read(),m=read();
	for(int i=1;i<=m;i++){  //因为点少边多,使用邻接矩阵存图更为方便
		int x=read(),y=read();
		g[x][y]=g[y][x]=min(g[x][y],read());
	}
	int ans=0x7fffffff;
	for(int i=1;i<=500;i++)  //为寻找最优解,多跑几遍
	  for(int i=1;i<=n;i++){  //枚举根节点
	  	  //初始化
		  memset(deth,127/3,sizeof(deth));
		  memset(vis,0,sizeof(vis));
		  deth[i]=vis[i]=1; top=0;
		  ans=min(ans,bfs(i))
	  }
	cout<<ans;
	return 0;
}
原文地址:https://www.cnblogs.com/morslin/p/11854967.html