数字梯形问题[网络流24题之16]
2016-05-23 14:04
369 查看
问题描述:
给定一个由 n 行数字组成的数字梯形如下图所示。梯形的第一行有 m 个数字。从梯形的顶部的 m 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。规则 1:从梯形的顶至底的 m 条路径互不相交。
规则 2:从梯形的顶至底的 m 条路径仅在数字结点处相交。
规则 3:从梯形的顶至底的 m 条路径允许在数字结点相交或边相交。
编程任务:
对于给定的数字梯形,分别按照规则 1 ,规则 2 ,和规则 3 计算出从梯形的顶至底的 m 条路径,使这 m 条路径经过的数字总和最大。数据输入:
第 1 行中有 2 个正整数 m 和 n(m,n<=20) ,分别表示数字梯形的第一行有 m 个数字,共有 n 行。接下来的 n 行是数字梯形中各行的数字。第 1 行有 m 个数字,第 2 行有 m+1 个数字 … 。结果输出:
输出共三行,分别是按照规则 1,规则 2,规则 3 计算出的最大数字总和。输入文件示例:
2 52 3
3 4 5
9 10 9 1
1 1 10 1 1
1 1 10 12 1 1
输出文件示例:
6675
77
分析:
记第 (i,j) (i 表示行, j 表示列 ) 个数字为 ai,j规则 1 因为这 m 条路径不能相交,所以可以考虑到拆点,即将第 (i,j) 个数字拆成 (i,j)0 和 (i,j)1 ,再从 (i,j)0 引一条容量为 1 ,权值为 0 的有向边向 (i,j)1,这样就保证了,每一个数字只会经过一遍,以下是构建图 N 过程
1 :新增源 S 和汇 T
2 :在 S 和 (1,j)0(1<=j<=m) 之间连一条容量为 1 ,权值为 −ai,j 的有向边
3 :在 (i,j)0(1<=i<=n,1<=j<i+m) 和 (i,j)1 之间连一条容量为 1 ,权值为 0 的有向边
4 :在 (i,j)1(1<=i<n,1<=j<i+m) 和 (i+1,j)0 之间连一条容量为 1 ,权值为 −ai+1,j 的有向边
5 :在 (i,j)1(1<=i<n,i<=j<i+m) 和 (i+1,j+1)0 之间连一条容量为 1 ,权值为 −ai+1,j+1 的有向边
6 :在 (n,j)1(1<=j<n+m) 和 T 之间连一条容量为 +∞ ,权值为 0 的有向边
图中的最小费用最大流的相反数就是规则 1 的答案
规则 2 :因为点可以经过任意次,所以只需要把(i,j)0(1<=i<=n,1<=j<i+m) 到 (i,j)1 的边的容量修改为 +∞ ,在求最小费用最大流即可,其相反数就是答案
规则 3 :因为边也可以随便走多少次了,所以只需要把除从 S 出发的边的容量修改为 +∞ ,在求最小费用最大流即可,其相反数就是答案
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int inf = 0x3f3f3f3f; int head[447],nxt[4747],to[4747],wei[4747],cost[4747],tot=1,tot1,tot2; queue<int >que; int dis[447],pre[447],pres[447]; bool vis[447]; int mark[47][47],a[47][47],re=0; int n,m; int used; void add(int,int,int,int); bool bfs(); void dinic(); int main(){ scanf("%d%d",&m,&n); for(int i=1;i<=n;++i) for(int j=1;j<=i+m-1;++j){ scanf("%d",&a[i][j]); mark[i][j] = ++re; add(mark[i][j]<<1,(mark[i][j]<<1)+1,1,0); } for(int i=1;i<n+m;++i) add((mark [i]<<1)+1,446,1,0); tot1 = tot; for(int i=1;i<n;++i) for(int j=1;j<m+i;++j){ add((mark[i][j]<<1)+1,mark[i+1][j]<<1,1,-a[i+1][j]); add((mark[i][j]<<1)+1,mark[i+1][j+1]<<1,1,-a[i+1][j+1]); } tot2 = tot; for(int i=1;i<=m;++i) add(445,mark[1][i]<<1,1,-a[1][i]); used = 0; while(bfs()) dinic(); printf("%d",-used); for(int i=2;i<tot;i+=2){ wei[i] += wei[i^1]; wei[i^1] = 0; } for(int i=2;i<tot1;++i) wei[i] = inf; used = 0; while(bfs()) dinic(); printf("\n%d",-used); for(int i=2;i<tot;i+=2){ wei[i] += wei[i^1]; wei[i^1] = 0; } for(int i=2;i<tot2;i+=2) wei[i] = inf; used = 0; while(bfs()) dinic(); printf("\n%d",-used); return 0; } void add(int from,int tp,int value,int spend){ ++tot;nxt[tot]=head[from];head[from]=tot;to[tot]=tp;wei[tot]=value;cost[tot]=spend; ++tot;nxt[tot]=head[tp];head[tp]=tot;to[tot]=from;wei[tot]=0;cost[tot]=-spend; } bool bfs(){ memset(vis,false,sizeof vis); memset(dis,0x3f,sizeof dis); dis[445] = 0; que.push(445); int now; do{ now = que.front(); vis[now] = false; que.pop(); for(int i=head[now];i;i=nxt[i]) if(dis[to[i]]>dis[now]+cost[i] && wei[i]){ dis[to[i]] = dis[now]+cost[i]; pre[to[i]] = now; pres[to[i]] = i; if(!vis[to[i]]){ vis[to[i]] = true; que.push(to[i]); } } }while(!que.empty()); return dis[446]!=inf; } void dinic(){ int now = 446; int low = inf; while(now != 445){ low = min(low,wei[pres[now]]); now = pre[now]; } used += low*dis[446]; now = 446; while(now != 445){ wei[pres[now]] -= low; wei[pres[now]^1] += low; now = pre[now]; } }
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- 解析C++中派生的概念以及派生类成员的访问属性