题目描述
给定一个由 nn 行数字组成的数字梯形如下图所示。
梯形的第一行有 mm 个数字。从梯形的顶部的 mm 个数字开始,在每个数字处可以沿左下或右下方向移动,形成一条从梯形的顶至底的路径。
分别遵守以下规则:
-
从梯形的顶至底的 mm 条路径互不相交;
-
从梯形的顶至底的 mm 条路径仅在数字结点处相交;
-
从梯形的顶至底的 mm 条路径允许在数字结点相交或边相交。
输入格式
第 11 行中有 22 个正整数 mm 和 nn,分别表示数字梯形的第一行有 mm 个数字,共有 nn 行。接下来的 nn行是数字梯形中各行的数字。
第 11 行有 mm 个数字,第 22 行有 m+1m+1 个数字,以此类推。
输出格式
将按照规则 11,规则 22,和规则 33 计算出的最大数字总和并输出,每行一个最大总和。
输入输出样例
输入 #1复制
2 5 2 3 3 4 5 9 10 9 1 1 1 10 1 1 1 1 10 12 1 1
输出 #1复制
66 75 77
说明/提示
1≤m,n≤20
思路:
点不能重合,那么拆点,将其流量设置为1,费用为点权。
点可以重合,但边不能重合,不用拆点,点权放在这个点的出边上,除了最后一排点的出边的流量为inf,其余的边的流量都为1。因为最后一排点到汇点的边是代表选择最后一个点,点能重合所以这里流量为inf。
最后边能重合,则将中间的边的流量设置为inf即可。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<vector>
#include<unordered_map>
#define mod (1000000007)
using namespace std;
typedef long long ll;
const int MAX = 2e5 + 5;
const int inf = 0x3f3f3f3f;
struct node {
int v,f,w,nxt;
} e[50005<<2];
int n,m,N;
int head[MAX],d[MAX],vis[MAX],tot=1,p[MAX];
void add(int u,int v,int f,int cost=0) {
e[++tot].v = v;e[tot].f = f;e[tot].w = cost;e[tot].nxt = head[u];head[u] = tot;
e[++tot].v = u;e[tot].f = 0; e[tot].w = -cost;e[tot].nxt = head[v];head[v] = tot;
}
bool bfs(int s,int t) {
for(int i = 0; i<=N; i++)
d[i]=inf,vis[i]=0;
d[s]=0;
queue<int>q;
q.push(s);
while(!q.empty()) {
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; ~i; i=e[i].nxt) {
int j=e[i].v;
if(e[i].f&&d[j]>d[u]+e[i].w) {
d[j]=d[u]+e[i].w;
p[j]=i;
if(!vis[j])vis[j]=1,q.push(j);
}
}
}
return d[t]<inf;
}
int MCMF(int s,int t,int &flow) {
int ans=0;
while(bfs(s,t)) {
int x=t,f=inf;
while(x!=s) {
f = min(f,e[p[x]].f),x=e[p[x]^1].v;
}
flow += f;
ans+=1LL*d[t]*f;
x=t;
while(x!=s) {
e[p[x]].f-=f,e[p[x]^1].f+=f;
x=e[p[x]^1].v;
}
}
return ans;
}
int a[50][50],id[50][50],ii=0;
int main() {
scanf("%d%d",&m,&n);
int st=0,ed,fl=0;N=ed;
for(int i=0;i<n;i++){
for(int j=1;j<=m+i;j++){
scanf("%d",&a[i+1][j]);
id[i+1][j]=++ii;
}
}
ed=2*ii+1;N=ed;//N记得赋初值
for(int i=0;i<=ed+1;i++) head[i]=-1;//上限设置小了
for(int i=1;i<=m;i++) add(st,id[1][i],1,0);
for(int i=1;i<=n;i++) for(int j=1;j<m+i;j++) add(id[i][j],id[i][j]+ii,1,-a[i][j]);
for(int i=1;i<n;i++) for(int j=1;j<m+i;j++) add(id[i][j]+ii,id[i+1][j],1,0),add(id[i][j]+ii,id[i+1][j+1],1,0);
for(int i=1;i<m+n;i++) add(id[n][i]+ii,ed,1,0);
int ans=-MCMF(st,ed,fl);
tot=1;
for(int i=0;i<=ed+1;i++) head[i]=-1;
for(int i=1;i<=m;i++) add(st,id[1][i],1,0);//只有m条路径,所以这里必须是1,不能是inf。
//因为边不重合,所以下面每条边只能走一次,但一个点的不同路径走的话都能获得这个点的值
for(int i=1;i<n;i++) for(int j=1;j<m+i;j++) add(id[i][j],id[i+1][j],1,-a[i][j]),add(id[i][j],id[i+1][j+1],1,-a[i][j]);
for(int i=1;i<m+n;i++) add(id[n][i],ed,inf,-a[n][i]);//这里为inf因为最后一个点可能被选多次,这里不存在边相互交叉的问题
int ans2=-MCMF(st,ed,fl);
tot=1;
for(int i=0;i<=ed+1;i++) head[i]=-1;
for(int i=1;i<=m;i++) add(st,id[1][i],1,0);
//边也可以走多次,则下面边的流量也设置为inf
for(int i=1;i<n;i++) for(int j=1;j<m+i;j++) add(id[i][j],id[i+1][j],inf,-a[i][j]),add(id[i][j],id[i+1][j+1],inf,-a[i][j]);
for(int i=1;i<m+n;i++) add(id[n][i],ed,inf,-a[n][i]);
int ans3=-MCMF(st,ed,fl);
printf("%d\n%d\n%d\n",ans,ans2,ans3);
return 0 ;
}