1. 程式人生 > 其它 >P3959 [NOIP2017 提高組] 寶藏 題解(狀壓dp)

P3959 [NOIP2017 提高組] 寶藏 題解(狀壓dp)

題目連結

題目大意

給定一個 n 個點 m 條邊的圖,請你求出一個有根樹。

滿足每個點的深度和它到父節點的邊權乘積之和最小。

n ≤ 12,m ≤ 1000

題目思路

本來我想的是設\(dp[i][s]\)表示以\(i\)為根節點,集合為\(s\)的最小答案但是發現根本轉移不了

考慮到點數只有12個,可以考慮狀態壓縮 DP。 用 s 表示當前加入的點集。

為了方便轉移,我們不記錄根是誰,而是直接去考慮深度。

也就是用$ dp[i][s] $表示當前的點集是 s,最深的點為 i。

然後我們去列舉 s 的補集的子集 t,把 t 都作為第 i+1 層加入 s。

我的程式碼複雜度為\(3^n*n+2^n*n^3\)

但是其實預處理\(dis\)陣列可以一次預處理出來,而我處理了\(n\)次,可以再優化一點

程式碼

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
const int maxn=12+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-6;
int n,m;
int e[maxn][maxn];
int dis[maxn];
int dp[maxn][1<<12];
signed main(){
    scanf("%d%d",&n,&m);
    memset(e,0x3f,sizeof(e));
    for(int i=1,u,v,w;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        u--,v--;
        e[v][u]=min(e[v][u],w);
        e[u][v]=e[v][u];
    }
    memset(dp,0x3f,sizeof(dp));
    for(int i=0;i<=n-1;i++){
        dp[0][1<<i]=0;
    }
    for(int i=0;i<=n-2;i++){
        for(int sta=0;sta<=(1<<n)-1;sta++){
            if(dp[i][sta]==inf) continue;
            for(int j=0;j<n;j++){
                dis[j]=inf;
                for(int k=0;k<n;k++){
                    if(sta&(1<<k)){
                        dis[j]=min(dis[j],e[j][k]);
                    }
                }
            }
            int zi=(((1<<n)-1)^sta);
            for(int j=zi;;j=((j-1)&zi)){
                int sum=0;
                for(int k=0;k<n;k++){
                    if(j&(1<<k)){
                        sum+=dis[k];
                    }
                    if(sum>=inf) break;
                }
                if(sum<inf){
                    dp[i+1][sta|j]=min(dp[i+1][sta|j],dp[i][sta]+sum*(i+1));
                }
                if(!j) break;
            }
        }
    }
    int ans=inf;
    for(int i=0;i<=n-1;i++){
        ans=min(ans,dp[i][(1<<n)-1]);
    }
    printf("%d\n",ans);
    return 0;
}

卷也卷不過,躺又躺不平