[NOIp2017] 宝藏

本文介绍了一种使用状态压缩动态规划解决特定宝藏挖掘问题的方法。问题设定在一个包含N个点的地图上,每个点藏有宝藏,通过打通边来挖掘宝藏,并优化挖掘总费用。文章详细解释了解题思路与算法实现,包括关键代码示例。

类型:状压

传送门:>Here<

题意:给出$N$个点藏有宝藏,有$M$条可以打通的边(都未打通)。你可以选任意一个点作为起点出发,每一次可以打通一条边来挖目的地的宝藏(不能打通已经挖完宝藏的两个点之间的边),其中打通一条边的费用是$L \times K$,其中$L$表示边的长度,$K$表示从当前点到起点打通的路径经过了几个点。问挖完所有宝藏的最小总费用

解题思路

由于$N \leq 12$,考虑状压。其中二进制状态$S$中为$1$的那一位表示已经挖掘

可以枚举起点$i$,设立数组$dp[S]$表示以$i$为起点且到达状态$S$所需要的最小费用,因此答案就是$Min\{ dp[2^n-1] \}$

然后从起点开始搜索,每一次枚举已经挖掘的所有点(设当前点为$j$,当前状态为$S$),再枚举这一次要打通的点$k$。记录$dis[j]$表示从$j$到起点经过的点的数量,也就是题目中的$K$。则$$dp[S|k] =  Min\{dp[S] + dis[j]*cost(j \rightarrow k)\}$$

这样的复杂度并不是$N!$,因为$dp$数组是有条件更新的(有点类似记搜)

Code

注意需要判断$i$到$j$有边

/*By DennyQi 2018.8.17*/
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
#define  r  read()
#define  Max(a,b)  (((a)>(b)) ? (a) : (b))
#define  Min(a,b)  (((a)<(b)) ? (a) : (b))
using namespace std;
typedef long long ll;
const int MAXN = 10010;
const int MAXM = 27010;
const int INF = 1061109567;
inline int read(){
    int x = 0; int w = 1; register int c = getchar();
    while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
    if(c == '-') w = -1, c = getchar();
    while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + c - '0', c = getchar();return x * w;
}
int N,M,x,y,z,ans;
int G[15][15],dp[8195],dis[15];
void DFS(int sta){
    for(int i = 1; i <= N; ++i){
        if(!(sta & (1<<(i-1)))) continue;
        for(int j = 1; j <= N; ++j){
            if(sta & (1<<(j-1))) continue;
            if(G[i][j] == INF) continue;
            if(dis[i] * G[i][j] + dp[sta] < dp[sta | (1<<(j-1))]){
                dp[sta | (1<<(j-1))] = dis[i] * G[i][j] + dp[sta];
                dis[j] = dis[i] + 1;
                DFS(sta | (1<<(j-1)));
            }
        }
    }
}
int main(){
    N=r,M=r;
    memset(G, 0x3f, sizeof G);
    ans = INF;
    for(int i = 1; i <= M; ++i){
        x=r,y=r,z=r;
        if(z < G[x][y]){
            G[x][y] = z;
            G[y][x] = z;
        }
    }
    for(int i = 1; i <= N; ++i){
        memset(dp, 0x3f, sizeof dp);
        memset(dis, 0, sizeof dis);
        dis[i] = 1;
        dp[1<<(i-1)] = 0;
        DFS(1<<(i-1));
        ans = Min(ans, dp[(1<<N) - 1]);
    }
    printf("%d", ans);
    return 0;
}

转载于:https://www.cnblogs.com/qixingzhi/p/9493636.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值