• 周五. 12月 9th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

P3959 [NOIP2017 提高组] 宝藏 题解(状压dp)

admin

11月 28, 2021

题目链接

题目大意

给定一个 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
",ans);
    return 0;
}

卷也卷不过,躺又躺不平

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注