P3959 [NOIP 2017 提高组] 宝藏 题解

link

题目要求任选图中一点为根,通过拓展道路最终形成一棵树,使得代价总和最小,代价受深度和边权两个因素影响。
容易想到一种爆搜,任选一点为根,每次扫描已选点来不断尝试拓展道路,但这样做太蛋疼了,我们尝试优化。

此时一看数据范围 \(n\le12\),考虑把思路往状压 \(dp\)上引导或寻找类似做法,我们发现:

  1. 在最终形成的树中,同一深度的点不受拓展先后顺序的影响,也就是说我们可以尝试一次性拓展同一深度的点。
  2. 对于已连通的点集 \(s\),我们不关心它的具体形态,只需保证其代价最小,因为在后续拓展中的代价与 \(s\) 的形态无关。

这样的条件启发我们可以 “深度” 为阶段,“点集” 为状态尝试设计 \(dp\)

\(dp\) 状态 \(f_{i,s}\) 为当前拓展到深度 \(i\),状态为 \(s\) 的点均连通时的最小代价,转移方程如下:

\[f_{i,s}=\min_{s \in z 且新点均与 z连通} \{f_{i-1,z} +(i-1)\times cost(z,s) \} \]

其中转移条件 \(s\in z\) 意味着新的一层只能打通原来没有的点,这时就需要用到子集枚举的手段,这样做时间复杂度是 \(O(3^n)\) 的。

\(cost(z,s)\) 表示从旧状态到新状态的代价,这个可以通过预处求得。

最终一层枚举起点,一层枚举深度,再加上子集枚举 \(dp\) 总时间复杂度是 \(O(n^2 3^n)\) 的,可以通过本题。

点击查看代码
const int N=15;
const int M=1<<12;
const int inf=0x3f3f3f3f;
int cost[M][M],n,m,e[N][N];
ll f[N][M],ans=inf;
void init(){
    for(int s=1;s<(1<<n);s++){   //当前层
        for(int z=s;z;z=(z-1)&s){//上一层为其子集,保证都是新点转移过来
            if(s==z) continue;
            for(int i=0,tmp=inf;i<n;i++,tmp=inf){    //枚举新点
                if(!( ((s^z)>>i) &1)) continue;
                for(int j=0;j<n;j++) if((z>>j)&1) tmp=min(tmp,e[j][i]); //选择最短边
                if(tmp>=inf){cost[z][s]=inf;break;}
                else cost[z][s]+=tmp;    
            }
        }
    }
}
ll DP(int x){
    memset(f,0x3f,sizeof(f));
    f[0][1<<x]=0;//初始免费边
    ll res=inf;
    for(int i=1;i<n;i++){
        for(int s=1;s<(1<<n);s++){
            for(int z=s;z;z=(z-1)&s){
                if(s==z) continue;
                f[i][s]=min(f[i][s],f[i-1][z]+1ll*i*cost[z][s]);
            }
            if(s==(1<<n)-1) res=min(res,f[i][s]);
        }
    }
    return res;
}
void xpigeon(){
    rd(n,m);
    if(n==1){
        cout<<0<<'\n';
        return ;
    }
    memset(e,0x3f,sizeof(e));
    for(int i=0;i<n;i++){
        e[i][i]=0;
    }
    for(int i=1,u,v,w;i<=m;i++){
        rd(u,v,w);
        u--,v--;
        e[u][v]=e[v][u]=min(e[u][v],w);
    }
    init();
    for(int i=0;i<n;i++) ans=min(ans,DP(i));
    cout<<ans<<'\n';
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);
    xpigeon();
    return 0;
}
posted @ 2025-09-23 15:38  香香的鸽子  阅读(14)  评论(0)    收藏  举报