【LuoguP3959】宝藏(NOIP2017)-状压DP:枚举子集

测试地址:宝藏
做法:本题需要用到状压DP。
dp(i,j)为深度为i,已开发的屋子状态为j的最小花费,那么我们枚举和j不相交的集合k,可以这样转移状态:
dp(i+1,j+k)=min(dp(i+1,j+k),dp(i,j)+dis(j,k)×(i+1))
其中dis(j,k)指集合k内的每个点到集合j中与它相邻的点的最小距离的和。
对于dis(j,k),我们可以先算出d(j,k):点k到集合j中相邻点的最小距离。这个显然可以O(n22n)求出。然后我们就可以算dis(j,k),由于kj不相交,所以枚举k实际上是枚举j的补集的子集,那么总的时间复杂度是O(n3n)的,可以接受。
那么最上面的状态转移方程就可以计算了,也是枚举子集的形式,所以总的时间复杂度是O(n3n),可以通过此题。
我傻逼的地方:没有特判n=1的情况(答案为0),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;
}
posted @ 2018-05-06 21:57  Maxwei_wzj  阅读(125)  评论(0编辑  收藏  举报