您的位置:首页 > 理论基础 > 计算机网络

数字梯形问题[网络流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 5

2 3

3 4 5

9 10 9 1

1 1 10 1 1

1 1 10 12 1 1

输出文件示例:

66

75

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];
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息