单源最短路建图,分层图

https://www.acwing.com/problem/content/905/

acwing 903.昂贵的聘礼

把每一个物品看作是一个点,此题是一定有解的,因为最坏的情况也是花费10000金币直接给酋长。所以这题将每个物品看作一个点,以替换的价值向替换的物品连边。就是一个最短路的问题。

不同的是,该题的物品是有等级划分的,也就是说要在一个等级区间内的物品才可以进行交易替换。所以我们要给最短路函数传入两个参数$up$和$down$,分别表示等级的上下界。然后以第一个物品的等级左右$m$为区间跑最短路算法取最小值。

还要注意的是,因为每个物品都可以直接购买,所以我们要建立一个虚拟源点0,指向每一个物品。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <queue>
 4 #include <cstring>
 5 
 6 using namespace std;
 7 
 8 const int N = 110, M = N * N, INF = 0x3f3f3f3f; 
 9 int n, m;
10 int level[N];//存储等级的数组
11 int e[M], ne[M], w[M], h[N], idx;
12 int dis[N];
13 bool st[N];
14 
15 void add(int a, int b, int c)
16 {
17     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
18 }
19 
20 int spfa(int down, int up)
21 {
22     memset(dis, 0x3f, sizeof dis);
23     queue<int> q;
24     q.push(0);
25     st[0] = true;
26     dis[0] = 0;
27     
28     while(q.size())
29     {
30         int t = q.front();
31         q.pop();
32         
33         st[t] = false;
34         
35         for(int i = h[t] ; ~ i ; i = ne[i])
36         {
37             int j = e[i];
38             if(level[j] <= up && level[j] >= down)
39             {
40                 if(dis[j] > dis[t] + w[i])
41                 {
42                     dis[j] = dis[t] + w[i];
43                     if(!st[j])
44                     {
45                         q.push(j);
46                         st[j] = true;
47                     }
48                 }
49             }
50         }
51     }
52     return dis[1];
53 }
54 
55 
56 
57 int main(){
58     cin >> m >> n;
59     
60     memset(h, -1, sizeof h);
61     for(int i = 1 ; i <= n ; i ++)
62     {
63         int price, cnt;
64         cin >> price >> level[i] >> cnt;
65         add(0, i, price);//每个物品都要有一个虚拟源点指向
66         
67         while(cnt --)
68         {
69             int id, cost;
70             cin >> id >> cost;
71             add(id, i, cost);//由该物品向可替换物品连边
72         }
73     }
74     
75     int ans = INF;
76     for(int i = level[1] - m ; i <= level[1] ; i ++)ans = min(ans, spfa(i, i + m));//循环等级区间的左端点
77     
78     cout << ans << endl;
79     return 0;
80 }

 

https://www.acwing.com/problem/content/1133/

acwing 1131.拯救大兵瑞恩

分层图。因为有门的存在需要有钥匙才可以过去,所以是需要有一维的参数是状态。考虑$dp$的思想,以$dis[x][y][state]$表示从起点走到$(x, y)$并且状态是state所花费的时间。由于本题捡起钥匙是不花费任何代价的,所以走到一个点有钥匙,就一定要将其拿起,,所以该点有钥匙的时候,很自然就能想到一个转移公式$dis[x][y][state | key] = dis[x][y][state]$。($ps:key$也是一个二进制数)。

而对于没有钥匙的情况,就是枚举上下左右四个方向,进行转移。

根据上面的分析,如果是有钥匙的转移,边权是0, 如果是四个方向的转移,边权为1,所以我们可以用双端队列,当边权为0加入队首,当边权为1,加入队尾。

另外,此为了方便起见,将图中的二维坐标映射为一维坐标,假设$n$为4, 即$(1, 1)$是1, $(1, 2)$是2,$(2, 1)$是5。这样就能将dis数组降为二维。

该题的图中有三种边,墙,无障碍,门。在读入时,将门建边,墙不建边(这样就等于无路可走),因为题目只读入墙和门,所以用一个$set$记录已经建过边的墙和门,剩下的都是属于无障碍,可以直接建边, 用一个$build$函数实现。

因为钥匙是从1开始,我们用二进制表示,就将钥匙的数映射少一位,从0开始。

  1 #include <iostream>
  2 #include <cstring>
  3 #include <algorithm>
  4 #include <deque>
  5 #include <set>
  6 
  7 #define x first
  8 #define y second
  9 using namespace std;
 10 
 11 typedef pair<int,int> PII;
 12 
 13 
 14 //对于一个n的图,每行的竖边有n - 1条,n条边就有n(n -1)条竖边,横边一样,并且还有双向边,所以总共有2 * 2 * n(n - 1)
 15 //n为10带入有360,开400。一共最多有十把钥匙,用二进制的十位数表示。
 16 const int N = 11, M = 400, P = 1 << 10;
 17 
 18 int e[M], ne[M], w[M], h[N * N], idx;
 19 int dis[N * N][P], g[N][N];
 20 bool st[N * N][P];
 21 int key[N * N];
 22 set<PII> edges;
 23 int n, m, k, p;
 24 
 25 void add(int a, int b, int c)
 26 {
 27     e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
 28 }
 29 
 30 void build()
 31 {
 32     int dx[4] = {0, 1, -1, 0}, dy[4] = {1, 0, 0, -1};
 33     
 34     for(int i = 1 ; i <= n ; i ++)
 35         for(int j = 1 ; j <= m ; j ++)
 36             for(int k = 0 ; k < 4 ; k ++)
 37             {
 38                 int x = i + dx[k], y = j + dy[k];
 39                 if(!x || !y || x > n || y > m)continue;
 40                 int a = g[i][j], b = g[x][y];
 41                 if(edges.count({a, b}) == 0)add(a, b, 0);//如果墙和门没建过,就是无障碍
 42             }
 43 }
 44 
 45 int bfs()
 46 {
 47     memset(dis, 0x3f, sizeof dis);
 48     dis[1][0] = 0;
 49     
 50     deque<PII> q;
 51     q.push_back({1, 0});
 52     
 53     while(q.size())
 54     {
 55         PII t = q.front();
 56         q.pop_front();
 57         
 58         if(st[t.x][t.y])continue;
 59         st[t.x][t.y] = true;
 60         
 61         if(t.x == n * m)return dis[t.x][t.y];
 62         
 63         if(key[t.x])//该点有钥匙的时候
 64         {
 65             int state = t.y | key[t.x];
 66             if(dis[t.x][state] > dis[t.x][t.y])
 67             {
 68                 dis[t.x][state] = dis[t.x][t.y];
 69                 q.push_front({t.x, state});
 70             }
 71         }
 72         
 73         for(int i = h[t.x] ; ~ i ; i = ne[i])//枚举四个方向
 74         {
 75             int j = e[i];
 76             if(w[i] && !(t.y >> w[i] - 1 & 1))continue;//如果这条路是门,但没有钥匙
 77             if(dis[j][t.y] > dis[t.x][t.y] + 1)
 78             {
 79                 dis[j][t.y] = dis[t.x][t.y] + 1;
 80                 q.push_back({j, t.y});
 81             }
 82         }
 83     }
 84     return -1;
 85 }
 86 
 87 int main(){
 88     cin >> n >> m >> p >> k;
 89     
 90     for(int i = 1, t = 1 ; i <= n ; i ++)//映射一维坐标
 91         for(int j = 1 ; j <= m ; j ++)
 92             g[i][j] = t ++;
 93     
 94     memset(h, -1, sizeof h);
 95     while(k --)
 96     {
 97         int x1, x2, y1, y2, c;
 98         cin >> x1 >> y1 >> x2 >> y2 >> c;
 99         int a = g[x1][y1], b = g[x2][y2];
100         edges.insert({a, b}), edges.insert({b, a});//记录墙和门
101         if(c)add(a, b, c), add(b, a, c);//如果不是墙是门就建边
102     }
103     
104     build();
105     
106     int s;
107     cin >> s;
108     while(s --)
109     {
110         int a, b, c;
111         cin >> a >> b >> c;
112         key[g[a][b]] |= 1 << c - 1;//该点有一个钥匙,坐标映射从0开始
113     }
114     
115     
116     cout << bfs() << endl;
117     
118     return 0;
119 }

 

posted @ 2020-03-21 00:34  dzcixy  阅读(121)  评论(0)    收藏  举报