P3959 宝藏

知识点 : 状压DP

原题面


分析题意:

任意指定起点
并且父亲点距离起点的距离 , 会影响边的权值

首先想到枚举出发点 ,
\(n\) 遍最短路 , 必然 \(TLE\)

观察数据范围 , 发现\(n\)非常小 ,
并且对于\(n\)个点在点集中的包含状况 ,
有重复的状态出现
考虑进行状压 \(dp\)


算法实现:

对 和起点已经联通的点 的集合 , 进行状态压缩
使用 \(f[i][j][k]\) 表示 ,
状态为 \(j\) , 深度 (\(dfs\)的深度)为\(k\) , 将第 \(i\) 个点加入集合 的代价
设置 \(k\) 的目的 , 是为了防止搜索深度不一 , 影响答案

则可以进行记忆化 \(dfs\)
\(dfs\) 传递参数 : 当前状态 \(now\) , 当前总代价 \(sum\) ,当前 \(dfs\) 深度\(dep\)
然后以当前状态中包含的点 ,
来疏导当前状态中不包含的点
并更新答案 , 将它们加入状态

答案即: 构造全集的最小代价


#include<cstdio>
#include<cstring>
#include<ctype.h>
#define min(a,b) a<b?a:b
//=================================================
const int MARX = 1e9+7;
int n,m,lim,  map[15][15],dis[15];
int ans=MARX , f[15][5000][15];
//=================================================
inline int read()
{
	int fl=1,w=0;char ch=getchar();
	while(!isdigit(ch) && ch!='-') ch=getchar();
	if(ch=='-') fl=-1;
	while(isdigit(ch)){w=w*10+ch-'0',ch=getchar();}
	return fl*w;
}
void dfs(int now , int sum , int dep)
{
	if(sum>=ans) return ;
	if(now==lim) {ans=min(ans,sum); return;}//当所有点都加入集合 , 取较小的答案 
	for(int i=1;i<=n;i++) 
	  if(1<<(i-1)&now)//枚举在集合中的点i 
	    for(int j=1;j<=n;j++)
	      if(!(1<<(j-1)&now) && map[i][j]<MARX)//枚举i可以到达的 , 且不在集合中的点j 
	  	  {
	  	    if(f[j][1<<(j-1)|now][dep+1] <= sum+dis[i]*map[i][j]) continue;//疏导后不优 
	  	    f[j][1<<(j-1)|now][dep+1] = sum+dis[i]*map[i][j];
	  	    dis[j]=dis[i]+1;//更改j的深度 
	  	    dfs(1<<(j-1)|now ,f[j][1<<(j-1)|now][dep+1] ,dep+1);//继续疏导 
		  }
}
//=================================================
signed main()
{
	n=read(),m=read();lim=(1<<n)-1;
	memset(map,MARX,sizeof(map));
	for(int i=1;i<=m;i++)//建图 
	{
	  int u=read(),v=read(),w=read();
	  map[u][v]=map[v][u]=min(map[u][v],w);
	}
	for(int i=1;i<=n;i++)//枚举出发点 
	{
	  memset(dis,0,sizeof(dis));//初始化 
	  memset(f,MARX,sizeof(f));
	  dis[i]=1; dfs(1<<(i-1),0,0);
	}
	printf("%d",ans);
}
posted @ 2019-09-14 20:59  Luckyblock  阅读(106)  评论(0)    收藏  举报