P1029 · OFFICIAL SOLUTION

P1029 沧溟探秘藏 官方题解

Gioush OJ · P1029 沧溟探秘藏

沧溟探秘藏

【题意简述】

给定 nn 个点、mm 条边的带权无向图。可以任选起点,之后每次从已探索点扩展一个未探索点。若从深度为 kk 的点经长度为 ww 的边扩展,则代价为 w(k+1)w(k+1)。求探索所有点的最小总代价。

【Hint】

nn 很小,可以用集合状态表示已经探索的点。由于代价与层数有关,转移时还需要枚举当前扩展发生在哪一层。

【提示】

Dpi,SDp_{i,S} 表示已经探索集合为 SS,当前准备扩展到第 ii 层时的最小代价。

【解法】

先用 Valu,vVal_{u,v} 记录两点之间最短的直接边权。令 CostS,T\operatorname{Cost}_{S,T} 表示从已探索集合 SS 向新增集合 TT 扩展一层时的最小边权和,其中每个 vTv\in T 都选择一条来自 SS 的最小边。

Dpk,SDp_{k,S} 表示完成若干层扩展后,已探索集合为 SS,当前最大层数为 kk 的最小代价。转移时枚举下一层新增集合 TT

Dpk+1,STmin(Dpk+1,ST,Dpk,S+(k+1)CostS,T).Dp_{k+1,S\cup T}\leftarrow \min(Dp_{k+1,S\cup T},Dp_{k,S}+(k+1)\operatorname{Cost}_{S,T}).

起点任意,因此所有单点集合初值为 00。答案为全集状态的最小值。

【转移含义】

固定一棵探索树后,同一深度的结点可以任意交换探索顺序,费用只取决于它们从此前已探索集合连出的最小边。因此用新增集合 TT 统一表示下一层,CostS,T\operatorname{Cost}_{S,T} 取每个 vTv\in TSS 的最小直接边权之和。

枚举 TST\subseteq\overline S 后,STS\cup T 正是完成一层扩展后的集合;乘上 k+1k+1 正是这层边的深度系数。每一棵合法探索树按层划分都会产生一条转移序列,反过来每条转移序列也能取最小边连接为一棵合法探索树。

【复杂度】

状态数量为 O(n2n)\mathcal{O}(n2^n),枚举子集后复杂度约为 O(3nn)\mathcal{O}(3^n n),适合 n12n\le12 的范围。

【参考代码】

/*Author:EhundateghDate:2026/7/21Name:treasure.cppYou steal,I kill.*/#include <vector>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;int n,m;int Val[13][13],In1,In2,In3,Pow[13]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096},B[4097];long long Dp[13][4097],Cost[13][4097],Ans=0x3f3f3f3f3f3f3f3f;int Lowbit(int x){return x&-x;}vector <int> Valid[4097];vector <long long> Pri[4097];int main(){    freopen("treasure.in","r",stdin);    freopen("treasure.out","w",stdout);    memset(Val,0x3f,sizeof(Val));    memset(Cost,0x3f,sizeof(Cost));    memset(Dp,0x3f,sizeof(Dp));    scanf("%d%d",&n,&m);    int Temp=1;    for(int i=1;i<=n;i++){        B[Temp]=i;        Temp<<=1;    }    for(int i=1;i<=m;i++){        scanf("%d%d%d",&In1,&In2,&In3);        Val[In1][In2]=min(Val[In1][In2],In3);        Val[In2][In1]=min(Val[In2][In1],In3);    }    for(int i=0;i<(1<<n);i++){        for(int j=1;j<=n;j++){            if((i&(1<<(j-1)))==0) continue;            for(int k=1;k<=n;k++){                Cost[k][i]=min(Cost[k][i],1ll*Val[k][j]);            }        }    }    for(int i=0;i<(1<<n);i++){        for(int j=(i-1)&i;;j=(j-1)&i){            int Diff=i-j; bool Tag=1;            long long Sum=0;            while(Diff){                if(Cost[B[Lowbit(Diff)]][j]>1e6){Tag=0; break;}                Sum+=Cost[B[Lowbit(Diff)]][j];                Diff-=Lowbit(Diff);            }            if(Tag){Valid[i].push_back(j); Pri[i].push_back(Sum);}            if(j==0) break;        }    }    for (int i=1;i<=n;i++) {        Dp[1][1<<(i-1)]=0;    }    for(int i=1;i<=n;i++){        for(int j=0;j<=(1<<n)-1;j++){            for(int k=0;k<Valid[j].size();k++){                Dp[i][j]=min(Dp[i][j],Dp[i-1][Valid[j][k]]+(i-1)*Pri[j][k]);            }        }        Ans=min(Ans,Dp[i][(1<<n)-1]);    }    printf("%lld\n",Ans);    return 0;}