noip2017 treasure(状压dp)

来源:互联网 发布:ipadmini2网络差 编辑:程序博客网 时间:2024/06/06 02:45

题目链接

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

一个朴素的想法是这样:
我们如果有一棵生成树,现在要往里面加入一个点
那么贪心的加边即可(枚举该点到生成树每一个点的连边)
当然上述做法显然有纰漏,如果这个点的最优路径端点不在生成树中,就不对了
但是如果给定一个加点序列,我们会发现上述贪心得出的是该排列下的最优解
所以枚举全排列,之后贪心即可
时间复杂度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;}
原创粉丝点击