题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1084
第1行:2个数M N,中间用空格分隔,为矩阵的大小。(2 <= M, N <= 200) 第2 - N + 1行:每行M个数,中间用空格隔开,对应格子中奖励的价值。(1 <= A[i,j] <= 10000)
输出能够获得的最大价值。
3 3 1 3 3 2 1 3 2 2 1
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 }
多线程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 }