noip2017 treasure(状压dp)

本文介绍了一种利用状态压缩动态规划(状压DP)解决特定道路生成树问题的方法。通过分析问题特点,采用枚举深度序列而非全排列的方式优化贪心策略,实现了时间复杂度为O(n³ⁿ)的高效算法。

题目链接

分析:
首先我们需要明确,最后我们打通的道路组成的一定是一个树形结构

一个朴素的想法是这样:
我们如果有一棵生成树,现在要往里面加入一个点
那么贪心的加边即可(枚举该点到生成树每一个点的连边)
当然上述做法显然有纰漏,如果这个点的最优路径端点不在生成树中,就不对了
但是如果给定一个加点序列,我们会发现上述贪心得出的是该排列下的最优解
所以枚举全排列,之后贪心即可
时间复杂度O(n!*n^2),会T4个点

那么仔细的看一下,其实贪心不仅是该选择排列下的最优解
还是深度序列下的最优解(每个节点在dfs树上都有一个深度)
我们枚举的全排列很多,但是对应的深度序列却很少
所以我们可以从深度考虑

这就是这道题的精髓所在了

n<=12,很符合状压dp的套路(不知道为什么CCF钟爱状压)
令f[i][S]表示当前与根连通的点的状态为S,并且最深的点的深度为i的最小代价
这样下一层的点就一定在S的补集SS
我们枚举SS的所有子集K,作为下一层的点集,
处理出K中每个点与S中的某个点连通所需要的最小代价

K中所有点的代价+f[i][S]去更新f[i+1][S|K]即可

时间复杂度O(n3^n)

tip

这道题的思路确实清奇一点
注意f的初始化

//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int INF=1e9;
int G[20][20],f[20][4100];
int n,m,tot,ans;

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=0;i<n;i++)
        for (int j=0;j<n;j++) G[i][j]=INF;
    for (int i=1;i<=m;i++)
    {
        int u,w,z;
        scanf("%d%d%d",&u,&w,&z);
        u--;w--;
        G[u][w]=min(G[u][w],z);
        G[w][u]=G[u][w];
    }

    for (int i=0;i<n;i++) for (int j=0;j<(1<<n);j++) f[i][j]=INF;
    for (int i=0;i<n;i++) f[0][1<<i]=0;          //边界条件 

    int i,j,k,x,a,b,xx;
    for (i=0;i<n;i++)                            //最深深度 
        for (x=0;x<(1<<n);x++)                   //S 
        if (f[i][x]!=INF)                        //该状态存在 
        {  
            xx=x^((1<<n)-1);                     //S的补集SS
            for (k=xx;k;k=(k-1)&xx)              //枚举SS的子集K
            {
                int rep=0;                       //总花费 
                for (a=0;a<n;a++)                //贪心 
                    if (k&(1<<a))                //a在K中
                    {
                        int v=INF;               //a连到生成树上的最小代价 
                        for (b=0;b<n;b++) if (x&(1<<b))   //枚举状态S中的点 
                            v=min(v,G[a][b]);
                        if (v==INF) goto ed;
                        rep+=v;
                    }  
                rep*=(i+1);                     //乘以深度(i从0枚举的)
                f[i+1][x|k]=min(f[i+1][x|k],f[i][x]+rep);

                ed:;
            }   
        }

    ans=INF;
    for (i=0;i<n;i++) ans=min(ans,f[i][(1<<n)-1]);
    printf("%d",ans);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值