【LuoguP3959】宝藏(NOIP2017)-状压DP:枚举子集
测试地址:宝藏
做法:本题需要用到状压DP。
令为深度为,已开发的屋子状态为的最小花费,那么我们枚举和不相交的集合,可以这样转移状态:
其中指集合内的每个点到集合中与它相邻的点的最小距离的和。
对于,我们可以先算出:点到集合中相邻点的最小距离。这个显然可以求出。然后我们就可以算,由于和不相交,所以枚举实际上是枚举的补集的子集,那么总的时间复杂度是的,可以接受。
那么最上面的状态转移方程就可以计算了,也是枚举子集的形式,所以总的时间复杂度是,可以通过此题。
我傻逼的地方:没有特判的情况(答案为),WA了一个点……当然如果状态转移方程边界条件考虑得更全的话可能不用特判。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,g[20][20],Dis[4210][4210],dis[4210][20],ans;
int dp[20][4210];
int main()
{
scanf("%d%d",&n,&m);
if (n==1) {printf("0");return 0;}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=inf;
for(int i=1;i<=m;i++)
{
int a,b,v;
scanf("%d%d%d",&a,&b,&v);
g[a][b]=min(g[a][b],v);
g[b][a]=g[a][b];
}
for(int i=0;i<(1<<n);i++)
for(int j=1;j<=n;j++)
if (!(i&(1<<(j-1))))
{
dis[i][j]=inf;
for(int k=1;k<=n;k++)
if (i&(1<<(k-1))) dis[i][j]=min(dis[i][j],g[k][j]);
}
for(int i=0;i<(1<<n);i++)
{
int anti=(1<<n)-i-1;
for(int j=anti;j;j=(j-1)&anti)
{
Dis[i][j]=0;
for(int k=1;k<=n;k++)
if (j&(1<<(k-1)))
{
if (dis[i][k]==inf)
{
Dis[i][j]=inf;
break;
}
Dis[i][j]+=dis[i][k];
}
}
}
for(int i=0;i<=n;i++)
for(int j=0;j<(1<<n);j++)
dp[i][j]=inf;
for(int i=1;i<=n;i++)
dp[0][1<<(i-1)]=0;
ans=inf;
for(int i=0;i<n;i++)
{
for(int j=0;j<(1<<n);j++)
if (dp[i][j]<inf)
{
int anti=(1<<n)-j-1;
for(int k=anti;k;k=(k-1)&anti)
if (Dis[j][k]<inf)
dp[i+1][j|k]=min(dp[i+1][j|k],dp[i][j]+Dis[j][k]*(i+1));
}
ans=min(ans,dp[i+1][(1<<n)-1]);
}
printf("%d",ans);
return 0;
}