洛谷 P3959 noip d2t2 宝藏

题目大意:给定一n个节点m条边的图,求权值最小的生成树。
权值的算法:以一个点为起点,每加一个点权值增加 起点到这个点的点数*所连边的权值。
n<=12,m<=1000;
题解:先考虑dfs,对于每个点dfs所有组合,复杂度很明显为O(n(n!)),对于12很明显过不去,但是这个题有70分n<=8,dfs能很轻松水到70分,所以很推荐写;
这个题目给的n的范围,就很状压,所以考虑状压dp;
定义:dpi,j表示在树高为i,状态为j时的最小价值;
j为当前讨论的集合 ,Cj为j关于全集的补集,设j’为Cj的子集,那么dpi,j就可以向dpi,j|j’转移,即:dpi,j|j’=min(dpi,j|j’,dpi,j+val j’);
只需要枚举所有的集合,并枚举其补集的子集,就可以枚举所有的状态,可以知道是能够达到最优解的;
已经知道了转移方式,那么问题就是求val j’,val j’=sigma(min dis(x,y) *(i+1))x,y分别属于j,j’,且所有x,y构成j,j’;
证明如下:
这个val j’的计算方法时认为所有j’中的点都连在了j中深度为i的节点上(即最深的一些点),那么如果有更优的解,即有点不是连在最深的节点上,那必然在循环中讨论到了,即在i小于当前i时就已经将更有的部分点转移到更小的i上并记录了,最终计算答案时会统计所有的解,也会将这种更优的讨论及由其转移来的状态统计到,不会漏解且一定为最优解。
考虑实现的方法:
先遍历所有的i值,然后遍历所有的状态数,枚举子集,计算所有的val值,然后进行dp转移;细节都在代码里,也有详细注释,可以阅读。

#include<bits/stdc++.h>
using namespace std;
const int maxn=(1<<12)+10;
const int INF=60000000;
int n,m,mp[15][15],lg[maxn],dp[13][maxn];
int mem[15],pos[maxn],tem[maxn],com[maxn];
void init()
{
	for(int i=0;i<n;i++)	for(int j=0;j<n;j++)	mp[i][j]=INF;
	for(int i=0;i<n;i++)	lg[1<<i]=i;
	memset(dp,0x3f,sizeof(dp));
	for(int i=0;i<n;i++)	dp[0][1<<i]=0;
}
inline int lowbit(int t){return t&(-t);}
int main()
{
	freopen("lg3959.in","r",stdin);
	freopen("lg3959.out","w",stdout);
	int x,y,z,ans=INF;
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&x,&y,&z);
		x--,y--;
		mp[x][y]=mp[y][x]=min(mp[x][y],z);
	}
	for(int i=0;i<n;i++)//深度 
		for(int con=0;con<(1<<n);con++){//condition
			int cnt=0;//记录其补集的大小 
			for(int now=0;now<n;now++)
				if(!(con&(1<<now))){//枚举当前状态补集 
					mem[cnt]=INF,pos[cnt]=1<<now;//记录当前点位置
					for(int j=con;j;j-=lowbit(j)){//枚举集合
						int nxt=lg[lowbit(j)];//提取枚举点位置
						mem[cnt]=min(mem[cnt],mp[now][nxt]*(i+1));
						//记录补集至集合连边最小代价 
					}
					cnt++;
				}
			for(int j=1;j<(1<<cnt);j++){//枚举其所有补集 
			//这里只枚举状态数,补集由下第二个公式计算 
				tem[j]=tem[j-lowbit(j)]+mem[lg[lowbit(j)]];//计算当前补集代价
				//由于循环方向,tem[j-lowbit(j)]都已经计算,可以递推
				 com[j]=com[j-lowbit(j)]|pos[lg[lowbit(j)]];//同理递推补集 
				 dp[i+1][con|com[j]]=min(dp[i+1][con|com[j]],dp[i][con]+tem[j]);
				 //dp方程,将x与其补集并集,比较大小 
			}
		}
	for(int i=0;i<=n;i++)	ans=min(ans,dp[i][(1<<n)-1]);
	/*for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++)
		if(dp[i][j]==0x3f3f3f3f)	printf("0 ");
		else printf("%d ",dp[i][j]);
		printf("\n");
	}*/
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值