目录
显示
题目
https://www.luogu.org/problem/P2045
题解
让我们建立一个网络流模型。
首先把每个点拆成入点和出点两个点,每个入点向它对应的出点连两条边:第一条容量为 \(1\),费用为该格子的数字,第二条边容量为 \(k-1\),费用为 \(0\)(代表多次经过该点但值只会取一次)。
每个出点向它能到的入点连边,容量为 \(k\),费用为 \(0\)(有 \(k\) 条路径)。
只需要跑一遍最大费用最大流即可。
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> #define INF 0x3f3f3f3f using namespace std; struct edge { int v,w,c,next; }e[50005]; struct pre { int v,e; }p[5005]; int head[5005],dist[5005],vis[5005],cnt=1,s,t; void addedge(int u,int v,int w,int c) { e[++cnt].v=v; e[cnt].w=w; e[cnt].c=c; e[cnt].next=head[u]; head[u]=cnt; } bool spfa() { queue<int> q; memset(dist,-INF,sizeof(dist)); memset(vis,0,sizeof(vis)); dist[s]=0; vis[s]=1; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i;i=e[i].next) if(e[i].w&&dist[u]+e[i].c>dist[e[i].v]) { dist[e[i].v]=dist[u]+e[i].c; p[e[i].v].e=i,p[e[i].v].v=u; if(!vis[e[i].v]) { vis[e[i].v]=1; q.push(e[i].v); } } } return dist[t]!=-INF; } int main() { int n,k; scanf("%d%d",&n,&k); s=1,t=2*n*n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { int x; scanf("%d",&x); addedge(n*(i-1)+j,n*n+n*(i-1)+j,1,x); addedge(n*n+n*(i-1)+j,n*(i-1)+j,0,-x); addedge(n*(i-1)+j,n*n+n*(i-1)+j,k-1,0); addedge(n*n+n*(i-1)+j,n*(i-1)+j,0,0); if(j<n) { addedge(n*n+n*(i-1)+j,n*(i-1)+j+1,k,0); addedge(n*(i-1)+j+1,n*n+n*(i-1)+j,0,0); } if(i<n) { addedge(n*n+n*(i-1)+j,n*i+j,k,0); addedge(n*i+j,n*n+n*(i-1)+j,0,0); } } int maxc=0; while(spfa()) { int minf=INF; for(int i=t;i!=s;i=p[i].v) minf=min(minf,e[p[i].e].w); if(!minf)break; for(int i=t;i!=s;i=p[i].v) { e[p[i].e].w-=minf; e[p[i].e^1].w+=minf; } maxc+=minf*dist[t]; } printf("%d\n",maxc); return 0; }