• 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
    ",ans);
        return 0;
    }
    
    
    卷也卷不过,躺又躺不平
  • 相关阅读:
    android4.0 及以上 版本 wifi 和 蓝牙不显示 原因
    AWK命令使用 小结
    Linux xargs命令 小结
    nginx rewrite伪静态配置参数详细说明
    简评file_get_contents与curl 效率及稳定性
    zendstudio 常用快捷键
    PHP字符串三种定义方式
    PHP连贯接口
    yii学习笔记
    PHP中str_replace函数的详解
  • 原文地址:https://www.cnblogs.com/hunxuewangzi/p/15179669.html
Copyright © 2020-2023  润新知