P2045 方格取数加强版题解
题目链接:P2045 方格取数加强版 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目:
出一个 n*n 的矩阵,每一格有一个非负整数 A{i,j}且A{i,j} <=10^3 现在从 (1,1) 出发,可以往右或者往下走,最后到达 (n,n),每达到一格,把该格子的数取出来,该格子的数就变成 0,这样一共走 K 次,现在要求 K 次所达到的方格的数的和最大。
解析:
一次方格取数原题是一个四位dp,但走k次的限制很难用dp在图上转移。
因为我们看到题目对每条边有限制,考虑用网络流,拆点建图来满足条件。
拆点可以得到两条边,(i,j)的入点向(i,j)的出点连一条费用为-1*(i,j)的值,流量限制为1(乘-1的原因是我们只能算最小费用最大流,这道题最大费用最大流,所以将费用取为负数,就可以做了),以及一条(i,j)的入点向(i,j)的出点连一条费用为0,流量限制为k-1的边。
同时由题意可知,(i,j)的出点向(i,j+1)和(i+1,j)连一条边,边的费用为0,流量限制为k。
再由网络流的性质,建超级源点向起点的入点连边和终点的出点向汇点连边。这些边的费用为0,流量限制为k,仅仅意味联通。
最后跑最小费用最大流即可
#include <bits/stdc++.h> using namespace std; const int N=100010; const int M=300010; const int INF=2139062143; int n,k,dis[N],pre[N],vis[N],flow[N],go[M],nxt[M],hd[N],jz[M],fl[M],tot=1; int id(int x,int y) { return (x-1)*n+y; } bool spfa(int s,int t) { memset(dis,0x7f,sizeof(dis)); memset(vis,0,sizeof(vis)); queue<int> q; q.push(s); dis[s]=0; vis[s]=1; flow[s]=INF; while(!q.empty()) { int u=q.front();q.pop(); vis[u]=0; for(int i=hd[u];i;i=nxt[i]) { int v=go[i]; if(fl[i]==0)continue; if(fl[i]>0&&dis[v]>dis[u]+jz[i]) { dis[v]=dis[u]+jz[i]; flow[v]=min(flow[u],fl[i]); pre[v]=i; if(!vis[v]) { q.push(v); vis[v]=1; } } } } if(dis[t]==INF)return 0; return 1; } long long ans=0,maxflow; void zdfy(int s,int t) { while(spfa(s,t))//图s,t是否联通 { maxflow+=flow[t]; ans+=dis[t]*flow[t];//一趟的价值为流量乘从s开始走的边的价值 int x=t; while(x!=s) { int las=pre[x]; fl[las]-=flow[t];//注意流量只消耗了flow[t] fl[las^1]+=flow[t]; x=go[las^1]; } } } void add(int x,int y,int j,int f) { nxt[++tot]=hd[x];go[tot]=y,jz[tot]=j,fl[tot]=f,hd[x]=tot; nxt[++tot]=hd[y];go[tot]=x,jz[tot]=-j,fl[tot]=0,hd[y]=tot; return ; }//dinic算法要反向边 int main() { scanf("%d%d",&n,&k); int s=0,t=2*n*n+1; add(s,id(1,1),0,k); add(id(n,n)+n*n,t,0,k); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { int x;scanf("%d",&x); add(id(i,j),id(i,j)+n*n,-x,1); add(id(i,j),id(i,j)+n*n,0,INF); if(i<n)add(id(i,j)+n*n,id(i+1,j),0,INF); if(j<n)add(id(i,j)+n*n,id(i,j+1),0,INF); } //建图 zdfy(s,t); printf("%lld\n",-1*ans);//答案取反 return 0; }