点边转化:把每个格子 (i,j) 拆成一个入点一个出点。
详解链接:【图论技巧】点边转化(拆点和拆边)
从每个入点向对应的出点连两条有向边:一条容量为 1 ,费用为格子 (i,j) 中的数;
另一条容量为 k−1 ,费用为 0 。
从 (i,j) 的出点到 (i,j+1) 和 (i+1,j) 的入点连有向边,容量为 k ,费用为 0 。
以 (1,1) 的入点为源点, (n,n) 的出点为汇点,求最大费用最大流。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<math.h>
#include<cstring>
#include<queue>
//#define ls (p<<1)
//#define rs (p<<1|1)
#define over(i,s,t) for(register int i = s;i <= t;++i)
#define lver(i,t,s) for(register int i = t;i >= s;--i)
//#define int __int128
//#define lowbit(p) p&(-p)
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const ll INF = 1e18;
const int N = 5e3+7;
const int M = 5e5+7;
int maxflow,s,t,k;
int n,m,ans,e;
int head[N],ver[M],nex[M],edge[M],cost[M],tot;
bool vis[N];
int dis[N],incf[N],pre[N];
void add(int x,int y,int z,int c){//正边反边
ver[++tot] = y;edge[tot] = z;cost[tot] = c;
nex[tot] = head[x];head[x] = tot;
ver[++tot] = x;edge[tot] = 0;cost[tot] = -c;
nex[tot] = head[y];head[y] = tot;
}
int num(int i,int j,int k){
return (i - 1) * n + j + k * n * n;
}
bool spfa(){//spfa求最长路
queue<int>q;
memset(vis,0,sizeof vis);
memset(dis,0xcf,sizeof dis);//-INF
q.push(s);
dis[s] = 0;vis[s] = 1;
incf[s] = 1<<30;//增广路各边的最小剩余容量
while(q.size()){
int x = q.front();q.pop();
vis[x] = 0;//spfa的操作
for(int i = head[x];i;i = nex[i]){
if(edge[i]){//剩余容量要>0,才在残余网络中
int y = ver[i];
if(dis[y] < dis[x] + cost[i]){
dis[y] = dis[x] + cost[i];
incf[y] = min(incf[x],edge[i]);//最小剩余容量
pre[y] = i;//记录前驱(前向星编号),方便找到最长路的实际方案
if(!vis[y])
vis[y] = 1,q.push(y);
}
}
}
}
if(dis[t] == 0xcfcfcfcf)
return false;//汇点不可达,已求出最大流
return true;
}
//EK的老操作了,更新最长增广路及其反向边的剩余容量
void update(){
int x = t;
while(x != s){
int i = pre[x];
edge[i] -= incf[t];
edge[i ^ 1] += incf[t];//成对变换,反边加
x = ver[i ^ 1];//反边回去的地方就是上一个结点
}
maxflow += incf[t];//顺便求最大流
ans += dis[t] * incf[t];//题目要求
}
void EK(){
while(spfa())//疯狂找增广路
update();
}
int main(){
cin>>n>>k;
s = 1;t = 2 * n * n;
tot = 1;
over(i,1,n)
over(j,1,n){
int c;
scanf("%d",&c);
add(num(i,j,0),num(i,j,1),1,c);//自己(入点0)与自己(出点1)
add(num(i,j,0),num(i,j,1),k-1,0);//两条边(取k次嘛,第一次有值,以后就没值了,用作下次选取)
if(i < n)add(num(i,j,1),num(i+1,j,0),k,0);//自己(出点1)与下一行(入点0)或者下一列(入点0)
if(j < n)add(num(i,j,1),num(i,j+1,0),k,0);
}
EK();
printf("%d\n",ans);
return 0;
}