51Nod 1084 矩阵取数问题 V2 —— 最小费用最大流 or 多线程DP

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1084

基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题
 收藏
 关注
一个M*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,先从左上走到右下,再从右下走到左上。第1遍时只能向下和向右走,第2遍时只能向上和向左走。两次如果经过同一个格子,则该格子的奖励只计算一次,求能够获得的最大价值。
 
例如:3 * 3的方格。
 
1 3 3
2 1 3
2 2 1
 
能够获得的最大价值为:17。1 -> 3 -> 3 -> 3 -> 1 -> 2 -> 2 -> 2 -> 1。其中起点和终点的奖励只计算1次。
 
Input
第1行:2个数M N,中间用空格分隔,为矩阵的大小。(2 <= M, N <= 200)
第2 - N + 1行:每行M个数,中间用空格隔开,对应格子中奖励的价值。(1 <= A[i,j] <= 10000)
Output
输出能够获得的最大价值。
Input示例
3 3
1 3 3
2 1 3
2 2 1
Output示例
17

题解:

1.实际上就是求两条从左上角到右下角的路径的权值和最大(重叠部分只算一次)。

2.感觉上,最优情况下的两条路径是不会有重叠部分的。但这只是感觉,缺乏证明,但就算两条路径可以有重叠部分,问题也可以照样解决。

3.一拿到题目首先就想到了DP,但是因为有两条路径,如果进行两次DP,在第一次DP后,把路径上的值全部清零,然后再进行一次DP,所得的结果不一定是最优的。因此:两条路径必须同时考虑,而不能割裂开来。

4.但是当时真的想不出怎么个DP,于是自己就朝着暴力一点的方向去思考,于是想到了图论之网络流,结果还真行得通:

如图:

1) 把每个格子拆成两点,一个点作为入点,一个点作为出点,从入点向出点引两条边,第一条边的容量为1,单位花费为-a[i][j],第二条的容量为1,单位花费为0。

2) 对于每个格子,从出点引一条边向右边格子的入点,容量为2,单位花费为0。

3) 以第一个格子的入点为源点,以最后一个格子的出点为汇点,跑最小费用最大流,得到的最小花费的相反数,即为答案。

正确性证明:

1) 由于每相邻两个点直接的容量都为2,所以就满足了“两条路径”。

2) 在一个格子里,入点和出点之间,只有一条边(一个容量)的花费是有效的,这就满足了“路径重叠部分的值只取一次”。

3) 最小费用最大流:可知最大流为2,这就满足了“两条路径”,而最小费用,即为最大费用的相反数,所以满足了权值和最大。

多线程DP做法:

1.可以在DP的时候,把两条路径也考虑进去。

2.设dp[step][x1][x2]:为走了step步,第一条路径的当前x坐标未x1,第二条的x坐标为x2,知道步数和x坐标,就可以知道y坐标了。详情请看代码。

(此题用网络流居然跑得比DP还快)

最小费用最大流:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <vector>
  6 #include <cmath>
  7 #include <queue>
  8 #include <stack>
  9 #include <map>
 10 #include <string>
 11 #include <set>
 12 #define ms(a,b) memset((a),(b),sizeof((a)))
 13 using namespace std;
 14 typedef long long LL;
 15 const int INF = 2e9;
 16 const LL LNF = 9e18;
 17 const int mod = 1e9+7;
 18 const int MAXN = 1e5+10;
 19 
 20 struct Edge
 21 {
 22     int to, next, cap, flow, cost;
 23 }edge[1000000];
 24 int tot, head[MAXN];
 25 int pre[MAXN], dis[MAXN];
 26 bool vis[MAXN];
 27 int N;
 28 
 29 void init(int n)
 30 {
 31     N = n;
 32     tot = 0;
 33     memset(head, -1, sizeof(head));
 34 }
 35 
 36 void add(int u, int v, int cap, int cost)
 37 {
 38     edge[tot].to = v; edge[tot].cap = cap; edge[tot].cost = cost;
 39     edge[tot].flow = 0; edge[tot].next = head[u]; head[u] = tot++;
 40     edge[tot].to = u; edge[tot].cap = 0; edge[tot].cost = -cost;
 41     edge[tot].flow = 0; edge[tot].next = head[v]; head[v] = tot++;
 42 }
 43 
 44 bool spfa(int s, int t)
 45 {
 46     queue<int>q;
 47     for(int i = 0; i<=N; i++)
 48     {
 49         dis[i] = INF;
 50         vis[i] = false;
 51         pre[i] = -1;
 52     }
 53 
 54     dis[s] = 0;
 55     vis[s] = true;
 56     q.push(s);
 57     while(!q.empty())
 58     {
 59         int u  = q.front();
 60         q.pop();
 61         vis[u] = false;
 62         for(int i = head[u]; i!=-1; i = edge[i].next)
 63         {
 64             int v = edge[i].to;
 65             if(edge[i].cap>edge[i].flow && dis[v]>dis[u]+edge[i].cost)
 66             {
 67                 dis[v] = dis[u]+edge[i].cost;
 68                 pre[v] = i;
 69                 if(!vis[v])
 70                 {
 71                     vis[v] = true;
 72                     q.push(v);
 73                 }
 74             }
 75         }
 76     }
 77     if(pre[t]==-1) return false;
 78     return true;
 79 }
 80 
 81 int minCostMaxFlow(int s, int t, int &cost)
 82 {
 83     int flow = 0;
 84     cost = 0;
 85     while(spfa(s,t))
 86     {
 87         int Min = INF;
 88         for(int i = pre[t]; i!=-1; i = pre[edge[i^1].to])
 89         {
 90             if(Min>edge[i].cap-edge[i].flow)
 91                 Min = edge[i].cap-edge[i].flow;
 92         }
 93         for(int i = pre[t]; i!=-1; i = pre[edge[i^1].to])
 94         {
 95             edge[i].flow += Min;
 96             edge[i^1].flow -= Min;
 97             cost += edge[i].cost*Min;
 98         }
 99         flow += Min;
100     }
101     return flow;
102 }
103 
104 int a[220][220];
105 int main()
106 {
107     int n, m;
108     scanf("%d%d",&m,&n);
109     for(int i = 0; i<n; i++)
110     for(int j = 0; j<m; j++)
111         scanf("%d",&a[i][j]);
112 
113     int N = n*m;
114     int st = 0, en = 2*N-1;
115     init(2*N);
116 
117     for(int i = 0; i<n; i++)
118     for(int j = 0; j<m; j++)
119     {
120         /*把格子拆成两个点,一个点进,一个点出,中间有两条边:
121           一条边的容量为1,单位花费为-a[i][j],
122           另一条的容量为1,单位花费为0.
123           这样就使得任意一个格子,可以在两条路径上,但只能被计算一次
124         */
125         add(i*m+j,N+i*m+j,1,-a[i][j]);
126         add(i*m+j,N+i*m+j,1,0);
127 
128         if(j!=m-1) add(N+i*m+j,i*m+j+1,2,0);    //引向右边的格子
129         if(i!=n-1) add(N+i*m+j,(i+1)*m+j,2,0);  //引向下边的格子
130     }
131 
132     int ans;
133     minCostMaxFlow(st,en,ans);
134     printf("%d
",-ans);    //取反
135 }
View Code

多线程DP:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <vector>
 6 #include <cmath>
 7 #include <queue>
 8 #include <stack>
 9 #include <map>
10 #include <string>
11 #include <set>
12 using namespace std;
13 typedef long long LL;
14 const int INF = 2e9;
15 const LL LNF = 9e18;
16 const int MOD = 1e9+7;
17 const int MAXN = 1e6+10;
18 
19 int a[220][220], dp[440][220][220];
20 int main()
21 {
22     int n, m;
23     while(scanf("%d%d",&m,&n)!=EOF)
24     {
25         for(int i = 0; i<n; i++)
26         for(int j = 0; j<m; j++)
27             scanf("%d",&a[i][j]);
28 
29         memset(dp, 0, sizeof(dp));
30         dp[0][0][0] = a[0][0];
31         for(int step = 1; step<=n+m-2; step++)
32         for(int x1 = 0; x1<=min(n-1,step); x1++)
33         for(int x2 = 0; x2<=min(n-1,step); x2++)
34         {
35             int y1 = step-x1;
36             int y2 = step-x2;
37             int flag = (x1!=x2)||(y1!=y2);  //判断两条路径的当前位置是否重叠
38             int t1 = 0, t2 = 0, t3 = 0, t4 = 0;
39             if(x1!=0&&x2!=0) t1 = dp[step-1][x1-1][x2-1];
40             if(x1!=0&&y2!=0) t2 = dp[step-1][x1-1][x2];
41             if(y1!=0&&x2!=0) t3 = dp[step-1][x1][x2-1];
42             if(y1!=0&&y2!=0) t4 = dp[step-1][x1][x2];
43             dp[step][x1][x2] = max(max(t1,t2),max(t3,t4))+a[x1][y1]+flag*a[x2][y2];
44         }
45 
46         printf("%d
", dp[n+m-2][n-1][n-1]);
47     }
48 }
View Code
原文地址:https://www.cnblogs.com/DOLFAMINGO/p/8735587.html