状压dp_宝藏

题目链接:https://www.luogu.org/problem/P3959

很显然开通的一定是树。

所以可以变相的认为赞助商搞的起点是树的根,每个结点到树的根的距离就是这个点的层数。

有点像树形dp的状压dp吧。枚举根节点,然后枚举每一层的结点的集合,算出这一集合的最小代价,存到f[floor][s]中,表示已经选了的(注意啊不是这一层选了的)结点的集合为s,选到了floor层的最小代价。

所以在递归的每一层中,枚举将要选的集合,依次判断集合中的点能否加入以及所有点都加入后的最小代价。用来更新这一层f的最小值。

PS:学到了一个技巧

枚举一个集合里面所以子集的方法:

for(int ns=mask;ns>0;ns=(ns-1)&mask)//ns就是mask的所有子集 

-1相当于去掉一个集合里面最后一个1,并且将他后面的所有0改成1。这样的话最可以从大到小枚举一个集合的所有子集。

#include <bits/stdc++.h>

using namespace std;
/*
状压的时候从0开始的好处:位移的时候不用写1<<(i-1)而是1<<i 
*/ 
const int Inf=1e9;
int n,m,f[15][5005],dis[15][15],vis[15][5005];
int find(int floor,int s)
{
	if(floor>n) return 1e9;
	if(s==(1<<n)-1) return 0;
	if(vis[floor][s]) return f[floor][s];
	vis[floor][s]=1;
	f[floor][s]=1e9;
	int mask=(1<<n)-1-s;//没有被选的集合 
	for(int ns=mask;ns>0;ns=(ns-1)&mask)//ns就是mask的所有子集 
	//这里就是枚举哪些不在集合外的点在这层可以塞到集合里面(最终形成的一定是一棵树啊)
	{
		int cur=0;//把ns里的点都塞进去的总代价
		for(int i=0;i<n;i++)
		{
			if((1<<i)&ns)
			{
				int c=Inf;
				for(int j=0;j<n;j++)
				{
					if(((1<<j)&s)&&dis[i][j]!=Inf)
					{
						c=min(c,dis[i][j]*floor);//这里因为是一棵树,floor就是离根节点的距离 
					} 
				}
				if(c==Inf) {cur=Inf;break;}
				cur+=c;
			}
		} 
		if(cur==Inf) continue;
		f[floor][s]=min(f[floor][s],find(floor+1,ns+s)+cur);
	}
	return f[floor][s];
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++)
			dis[i][j]=1e9; 
	for(int i=1;i<=m;i++)
	{
		int x,y,z;scanf("%d%d%d",&x,&y,&z);
		x--;y--;dis[x][y]=min(dis[x][y],z);
		dis[y][x]=min(dis[y][x],z);
	}
	int minv=1e9;
	for(int i=0;i<n;i++)//i作为起始点 
	{
		minv=min(minv,find(1,1<<i));
	}
	printf("%d",minv);
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值