2018.10.12【NOIP2017】【洛谷P3659】宝藏(状压DP)

传送门


解析:

看到数据范围只有 12 12 12,不会还有人以为这是一道最小生成树的魔改题吧。。。
这道题与最小生成树似乎什么关系都没有。。。(虽然听说最小生成树可以骗到45分)

思路:

首先看到数据范围只有 12 12 12。。。
可以,这很状压。

那么我们显然状压的就是当前有哪些点已经选在了我们当前的生成树中,然而这里开始出现了难以理解的东西,我们只需要维护当前生成树的树高。

我先给出状态转移方程,再谈论上述解法的正确性。
f s , h f_{s,h} fs,h表示在选取 s s s集合中的点加入生成树的时候,树高为 h h h的最小代价。

状态转移方程如下 f s , h = m i n { f s 0 , h − 1 + h × t o t } ( s ∈ G s 0 ) f_{s,h}=min\{f{s_0},h-1+h\times tot\}(s\in G_{s_0}) fs,h=min{fs0,h1+h×tot}(sGs0)

其中 G s 0 G_{s_0} Gs0 s 0 s_0 s0中的点能够通过一次扩展边而加入点产生的生成树的新的点集。 t o t tot tot是新加入的点通过权值最小的边与原来在集合中的点相连的代价和。

显然所有的 G G G可以预处理出来。
那么为什么可以这样转移呢?

对于一个集合 s s s s 0 s_0 s0,如果他们之间的边不是被 s 0 s_0 s0中最大深度的点连接成的,那么一定存在另一个 s 1 s_1 s1,包含另一种连边的情况使得 s 1 s_1 s1包含除被最大深度点连接的点以外的所有点,那么通过 s 1 s_1 s1转移的答案就是最小值,一定是正确的。

代码实现中我们将所有节点编号-1,然后就可以愉快用位运算状压了。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const 

inline int getint(){
	re int num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
}

cs int N=15,INF=0x3f3f3f3f;
int dist[N][N];
int cost[1<<N][N];
int broaden[1<<N];

int n,m;
signed main(){
	memset(dist,0x3f,sizeof dist);
	n=getint();
	m=getint();
	for(int re i=1;i<=m;++i){
		int u=getint()-1,v=getint()-1,val=getint();
		dist[u][v]=dist[v][u]=min(dist[u][v],val);
	}
	for(int re i=0;i<n;++i)dist[i][i]=0;
	for(int re i=1;i<(1<<n);++i){
		for(int re j=0;j<n;++j){
			if((1<<j)&i){
				for(int re k=0;k<n;++k)
				if(dist[j][k]<INF)broaden[i]|=1<<k;
			}
		}
	}
	memset(cost,0x3f,sizeof cost);
	for(int re i=0;i<n;++i)cost[1<<i][0]=0;
	for(int re i=2;i<(1<<n);++i){
		for(int re subset=(i-1);subset;subset=(subset-1)&i){
			if((i|broaden[subset])==broaden[subset]){
				int coms=i^subset;
				int tot=0;
				for(int re k=0;k<n;++k)
				if((1<<k)&coms){
					int tmp=INF;
					for(int re t=0;t<n;++t)if(1<<t&subset)
					tmp=min(tmp,dist[k][t]);
					tot+=tmp;
				}
				for(int re j=1;j<n;++j)
				cost[i][j]=min(cost[i][j],cost[subset][j-1]+tot*j);
			}
		}
	}
	int ans=INF;
	for(int re i=0;i<n;++i)ans=min(ans,cost[(1<<n)-1][i]);
	cout<<ans;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值