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

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

猜你喜欢

转载自blog.csdn.net/maxwei_wzj/article/details/80219074