P3959 [NOIP2017 提高组] 宝藏(状压DP)

题目链接

思路

  • 首先,我们看到数据范围1≤n≤12,这么小的n,我们很快就可以想到状压DP。
  • 设计f[i][j]表示挖第1~i层,已经挖了的状态为j时的代价,那么状态转移方程就有了:
f[i][j]=min(f[i][j],f[i-1][k]+(i-1)*cost[k][j]);
  • 其中k是j的子集,j是k扩展一层能到达的状态的子集(用Accessible[k]表示),Accessible可以预处理出来:
for(ri i=0;i<1<<n;++i)
{
	int ans=0;
	for(ri j=0;j<n;++j)
		if(i&(1<<j))
		{
			ans|=1<<j;
			for(ri k=0;k<n;++k)
				if(w[j][k]!=w[0][0]) ans|=1<<k;
		}
	Accessible[i]=ans;
}
  • 其中cost[k][j]表示从状态k转移到状态j所需添加的道路的总长度,也可以预处理出来:
for(ri i=0;i<1<<n;++i)
	for(ri j=i;j;j=(j-1)&i)//j是i的子集
		if(j!=i) Calculate_cost(j,i);
int Calculate_cost(int a,int b){
	int c=a^b,ans=0;//c即为b比a多的路
	for(ri i=0;i<n;++i)//为方便起见,把节点都减1
		if(c&(1<<i))//判断c是否已打通i号节点
		{
			int mi=w[0][0];//w[0][0]是inf=2139062143
			for(ri j=0;j<n;++j)
				if(a&(1<<j))//判断a是否已打通j号节点
					mi=min(mi,w[j][i]);
					//打通已通点j到所需打通的点i的最小长度
			if(mi!=w[0][0]) ans+=mi;
		}
	return cost[a][b]=ans;
}
  • 枚举i的子集jj=i;j;j=(j-1)&i;如果a属于b,则有b=a|b,a=a&b
  • DP过程:
for(ri i=2;i<=n;++i)
	for(ri j=1;j<1<<n;++j)
		for(ri k=j;k;k=(k-1)&j)//枚举j的子集
			if(k!=j&&sy(j,Accessible[k]))//判断k状态是否可以转移为j状态
				f[i][j]=min(f[i][j],f[i-1][k]+(i-1)*cost[k][j]);//转移
  • 关于初始状态:因为起点是任意的,所以for(ri i=0;i<n;++i) f[1][1<<i]=0;,这样就省去了枚举起点。
  • 注意开long long

代码

#include<cstdio>
#include<cstring>
#define int long long
#define ri register int
#define min(a,b) a<b?a:b
#define max(a,b) a>b?a:b
using namespace std;
const int maxn=(1<<12)+21;
int n,m,ans;
int w[15][15];
int f[15][maxn];
int Accessible[maxn];
int cost[maxn][maxn];
int read(){
	int x=0;char c=getchar();
	while(c>'9'||c<'0') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();
	return x;
}
int Calculate_cost(int a,int b){
	if(cost[a][b]) return cost[a][b];
	int c=a^b,ans=0;
	for(ri i=0;i<n;++i)
		if(c&(1<<i))
		{
			int mi=w[0][0];
			for(ri j=0;j<n;++j)
				if(a&(1<<j))
					mi=min(mi,w[j][i]);
			if(mi!=w[0][0]) ans+=mi;
		}
	return cost[a][b]=ans;
}
void pre(){
	for(ri i=0;i<1<<n;++i)
	{
		int ans=0;
		for(ri j=0;j<n;++j)
			if(i&(1<<j))
			{
				ans|=1<<j;
				for(ri k=0;k<n;++k)
					if(w[j][k]!=w[0][0]) ans|=1<<k;
			}
		Accessible[i]=ans;
	}
}
bool sy(int a,int b){
	return b==(a|b);
}
void solve(){
	memset(f,127,sizeof f);
	for(ri i=0;i<1<<n;++i)
		for(ri j=i;j;j=(j-1)&i)
			if(j!=i) Calculate_cost(j,i);
	for(ri i=0;i<n;++i) f[1][1<<i]=0;
	for(ri i=2;i<=n;++i)
		for(ri j=1;j<1<<n;++j)
			for(ri k=j;k;k=(k-1)&j)
				if(k!=j&&sy(j,Accessible[k]))
					f[i][j]=min(f[i][j],f[i-1][k]+(i-1)*cost[k][j]);
	for(ri i=1;i<=n;++i) ans=min(ans,f[i][(1<<n)-1]);
}
signed main(){
	n=read(),m=read();
	memset(w,127,sizeof w);
	while(m--)
	{
		int u=read()-1,v=read()-1,w1=read();
		w[u][v]=w[v][u]=min(w[u][v],w1);
	}	
	pre();
	ans=w[0][0];
	solve();
	printf("%lld",ans);
	return 0;
}

我的状压DP题单

另一种解法:随机化贪心

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Robin_w2321

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值