网络流24题——数字梯形问题 luogu 4013
题目描述:这里
极其裸的一道费用流问题
首先分析第一问,由于要求一个点只能经过一次,所以我们将梯形中的每一个点拆成两个点(记为入点和出点,顾名思义,入点用来承接上一行向这一行的边,出点用来向下一行连边)
然后将出入点之间的流量设为1,边权设为0,这样就有效保证了一个点只经过一次
接下来,我们从上一行的出点向下一行的入点连边,容量为1,费用为下一行对应点的代价的相反数,然后建起超级源点与超级终点,分别向第一行的入点连边,容量1费用0,由最后一行出点向终点连边,容量1费用0,跑一遍费用流即可(就是套路的最大费用流)
然后看第二问,发现点可以重复经过,这样就不用拆点了,但边不能重复走,所以我们不拆点,剩下的建边与上面相同
但是注意:两条路径可以相交于最后一行,这样的话如果最后一行的点向汇点连边的容量为1是不够的,所以设的容量要大于等于2
第三问就是把除了源点到第一行的边以外的边边权全改为正无穷即可
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long using namespace std; const ll inf=0x3f3f3f3f3f3f3f3fll; struct Edge { int next; int to; ll val; ll pri; }edge[10005]; int head[1005]; int nnum[55][55]; ll a[55][55]; ll dis[1005]; int pre[1005]; int fa[1005]; ll lim[1005]; bool used[1005]; int cnt=1; int tot=0; int st,ed; int n,m; void init() { memset(edge,0,sizeof(edge)); memset(head,-1,sizeof(head)); cnt=1; } void add(int l,int r,ll w,ll v) { edge[cnt].next=head[l]; edge[cnt].to=r; edge[cnt].val=w; edge[cnt].pri=v; head[l]=cnt++; } int ide(int x) { return (x&1)?x+1:x-1; } bool spfa() { memset(dis,0x3f,sizeof(dis)); memset(lim,0,sizeof(lim)); memset(used,0,sizeof(used)); used[st]=1; lim[st]=inf; dis[st]=0; pre[ed]=-1; queue <int> M; M.push(st); while(!M.empty()) { int u=M.front(); M.pop(); for(int i=head[u];i!=-1;i=edge[i].next) { int to=edge[i].to; if(edge[i].val&&dis[to]>dis[u]+edge[i].pri) { dis[to]=dis[u]+edge[i].pri; lim[to]=min(lim[u],edge[i].val); pre[to]=i,fa[to]=u; if(!used[to])used[to]=1,M.push(to); } } used[u]=0; } return pre[ed]!=-1; } ll EK() { ll maxw=0,minv=0; while(spfa()) { maxw+=lim[ed]; minv+=dis[ed]*lim[ed]; int temp=ed; while(temp!=st) { edge[pre[temp]].val-=lim[ed]; edge[ide(pre[temp])].val+=lim[ed]; temp=fa[temp]; } } return minv; } int main() { scanf("%d%d",&m,&n); init(); st=0,ed=1; for(int i=1;i<=n;i++) { for(int j=1;j<=m+i-1;j++) { scanf("%lld",&a[i][j]); nnum[i][j]=++tot; } } for(int i=1;i<=n;i++) { for(int j=1;j<=m+i-1;j++) { add(nnum[i][j]<<1,(nnum[i][j]<<1)|1,1,-a[i][j]); add((nnum[i][j]<<1)|1,nnum[i][j]<<1,0,a[i][j]); if(i==1) { add(st,nnum[i][j]<<1,1,0); add(nnum[i][j]<<1,st,0,0); } if(i==n) { add((nnum[i][j]<<1)|1,ed,1,0); add(ed,(nnum[i][j]<<1)|1,0,0); }else { add((nnum[i][j]<<1)|1,(nnum[i+1][j]<<1),1,0); add((nnum[i+1][j]<<1),(nnum[i][j]<<1)|1,0,0); add((nnum[i][j]<<1)|1,(nnum[i+1][j+1]<<1),1,0); add((nnum[i+1][j+1]<<1),(nnum[i][j]<<1)|1,0,0); } } } printf("%lld\n",-EK()); init(); for(int i=1;i<=n;i++) { for(int j=1;j<=m+i-1;j++) { if(i==1) { add(st,nnum[i][j]+1,1,-a[i][j]); add(nnum[i][j]+1,st,0,a[i][j]); } if(i==n) { add(nnum[i][j]+1,ed,2,0); add(ed,nnum[i][j]+1,0,0); }else { add(nnum[i][j]+1,nnum[i+1][j]+1,1,-a[i+1][j]); add(nnum[i+1][j]+1,nnum[i][j]+1,0,a[i+1][j]); add(nnum[i][j]+1,nnum[i+1][j+1]+1,1,-a[i+1][j+1]); add(nnum[i+1][j+1]+1,nnum[i][j]+1,0,a[i+1][j+1]); } } } printf("%lld\n",-EK()); init(); for(int i=1;i<=n;i++) { for(int j=1;j<=m+i-1;j++) { if(i==1) { add(st,nnum[i][j]+1,1,-a[i][j]); add(nnum[i][j]+1,st,0,a[i][j]); } if(i==n) { add(nnum[i][j]+1,ed,inf,0); add(ed,nnum[i][j]+1,0,0); }else { add(nnum[i][j]+1,nnum[i+1][j]+1,inf,-a[i+1][j]); add(nnum[i+1][j]+1,nnum[i][j]+1,0,a[i+1][j]); add(nnum[i][j]+1,nnum[i+1][j+1]+1,inf,-a[i+1][j+1]); add(nnum[i+1][j+1]+1,nnum[i][j]+1,0,a[i+1][j+1]); } } } printf("%lld\n",-EK()); return 0; }