AcWing 382. K取方格数(费用流)

2024-04-16 00:38
文章标签 acwing 费用 方格 382

本文主要是介绍AcWing 382. K取方格数(费用流),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在一个N*N的矩形网格中,每个格子里都写着一个非负整数。

可以从左上角到右下角安排K条路线,每一步只能往下或往右,沿途经过的格子中的整数会被取走。

若多条路线重复经过一个格子,只取一次。

求能取得的整数的和最大是多少。

输入格式
第一行包含两个整数N和K。

接下来N行,每行包含N个不超过1000的整数,用来描述整个矩形网格。

输出格式
输出一个整数,表示能取得的最大和。

数据范围
1≤N≤50,
0≤K≤10
输入样例:
3 2
1 2 3
0 2 1
1 4 2
输出样例:
15

思路:
费用流就是,每个边在有容量限制 c ( x , y ) c(x,y) c(x,y)的基础上,再增加了单位费用 w ( x , y ) w(x,y) w(x,y)
当这个边的流量为 f ( x , y ) f(x,y) f(x,y)时,对应费用为 f ( x , y ) ∗ w ( x , y ) f(x,y)*w(x,y) f(x,y)w(x,y)
在最大流的基础上求最小(最大)费用,称为费用流。

本题要求从 ( 1 , 1 ) (1,1) (1,1)经过 k k k条路径到 ( n , n ) (n,n) (n,n),可以作为容量为 k k k的最大流。
且每个格子只取一次,而费用则与格子的数有关。

本题就可以将每个格子拆成入点和出点。
入点向出点连两条边,一条容量为1,费用为格子值;
另一条边容量为k-1,费用为0。

然后每个格子的出点向相邻右边和下边的格子的入点连一条容量为 k k k,费用为 0 0 0的边。

跑出费用流就是结果了。

spfa费用流

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 5010, M = 200010;
int ver[M], edge[M], cost[M], Next[M], head[N];
int d[N], incf[N], pre[N], v[N];
int n, k, tot, s, t, maxflow, ans;void add(int x, int y, int z, int c) {// 正向边,初始容量z,单位费用cver[++tot] = y, edge[tot] = z, cost[tot] = c;Next[tot] = head[x], head[x] = tot;// 反向边,初始容量0,单位费用-c,与正向边“成对存储”ver[++tot] = x, edge[tot] = 0, cost[tot] = -c;Next[tot] = head[y], head[y] = tot;
}bool spfa() {queue<int> q;memset(d, 0xcf, sizeof(d)); // -INFmemset(v, 0, sizeof(v));q.push(s); d[s] = 0; v[s] = 1; // SPFA 求最长路incf[s] = 1 << 30; // 增广路上各边的最小剩余容量while (q.size()) {int x = q.front(); v[x] = 0; q.pop();for (int i = head[x]; i; i = Next[i]) {if (!edge[i]) continue; // 剩余容量为0,不在残量网络中,不遍历int y = ver[i];if (d[y]<d[x] + cost[i]) {d[y] = d[x] + cost[i];incf[y] = min(incf[x], edge[i]);pre[y] = i; // 记录前驱,便于找到最长路的实际方案if (!v[y]) v[y] = 1, q.push(y);}}}if (d[t] == 0xcfcfcfcf) return false; // 汇点不可达,已求出最大流return true;
}// 更新最长增广路及其反向边的剩余容量
void update() {int x = t;while (x != s) {int i = pre[x];edge[i] -= incf[t];edge[i ^ 1] += incf[t]; // 利用“成对存储”的xor 1技巧x = ver[i ^ 1];}maxflow += incf[t];ans += d[t] * incf[t];
}int num(int i,int j,int k) {return (i - 1) * n + j + k * n * n;
}int main() {scanf("%d%d",&n,&k);s = 1,t = 2 * n * n;tot = 1;for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {int c;scanf("%d",&c);add(num(i,j,0),num(i,j,1),1,c); //表示取这个数,流量为1,只能取一次add(num(i,j,0),num(i,j,1),k-1,0); //不取这个数,流量为k-1if(j < n) add(num(i,j,1),num(i,j + 1,0),k,0);if(i < n) add(num(i,j,1),num(i + 1,j,0),k,0);}}while(spfa()) update();printf("%d\n",ans);return 0;
}

dij费用流
引入“势函数”,也就是每个边加权,避免负边

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <queue>
#include <math.h>
using namespace std;#define INF 0x3f3f3f3fint n , k, tot = 0;
const int N = 55;
struct e
{int t;int c;int w;int next;
}edge[N*N*N];
int head[N*N*N] , h[N*N*N], dis[N*N*N], pre[N*N*N], pc[N*N*N], st, ed;
int ansflow , anscost, cnt;
void add(int f, int t, int c, int w)
{edge[cnt].t = t;edge[cnt].c = c;edge[cnt].next = head[f];edge[cnt].w = w;head[f] = cnt ++;edge[cnt].t = f;edge[cnt].c = 0;edge[cnt].next = head[t];edge[cnt].w = -w;head[t] = cnt ++;
}
struct node{int v, val;node(int a, int b):v(a), val(b){}bool operator < (const node& no) const{// return val > no.val; //最小费用return val < no.val; //最大费用}
};
void dij()
{priority_queue < node > q;while(true) //从汇点出发,不断寻找花费最短路作为增广路{for ( int i = 0 ; i <= ed ; i ++)dis[i] = -999999999;dis[st] = 0;pre[st] = -1;pc[st] = INF; //用于记录该增广路所能承受的最大流q.push(node(st,0));while(!q.empty()) //采用堆优化的dij寻找{node p = q.top();q.pop();int u = p.v;if(p.val < dis[u]) continue;for(int i = head[u] ; i != -1 ; i = edge[i].next){int v = edge[i].t , c = edge[i].c, w = edge[i].w;int ww = w + h[u] - h[v]; //引入势函数作为新权,保证正数,从而让成功,类似于Johnson算法if(c > 0 && dis[u] + ww > dis[v]){dis[v] = dis[u] + ww; //修改距离q.push(node(v,dis[v]));pre[v] = i; //记录路径pc[v] = min(pc[u],c); //修改最大流}}}if(dis[ed] == -999999999) //如果为INF说明不存在增广路,跳出即可break;for(int i = 1 ; i <= ed ; i ++) h[i] += dis[i]; //更新势函数int nowflow = pc[ed];ansflow += nowflow;anscost += h[ed]*nowflow;for(int i = ed ; pre[i] != -1 ; i = edge[pre[i]^1].t) //递归回溯路径,改变流大小{edge[pre[i]].c -= nowflow;edge[pre[i]^1].c += nowflow;}}
}int num(int i,int j,int k) {return (i - 1) * n + j + k * n * n;
}int main() {memset(head,-1,sizeof(head));scanf("%d%d",&n,&k);st = 1,ed = 2 * n * n;for(int i = 1;i <= n;i++) {for(int j = 1;j <= n;j++) {int c;scanf("%d",&c);add(num(i,j,0),num(i,j,1),1,c); //表示取这个数,流量为1,只能取一次add(num(i,j,0),num(i,j,1),k-1,0); //不取这个数,流量为k-1if(j < n) add(num(i,j,1),num(i,j + 1,0),k,0);if(i < n) add(num(i,j,1),num(i + 1,j,0),k,0);}}dij();printf("%d\n",anscost);return 0;
}

这篇关于AcWing 382. K取方格数(费用流)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/907360

相关文章

poj 2175 最小费用最大流TLE

题意: 一条街上有n个大楼,坐标为xi,yi,bi个人在里面工作。 然后防空洞的坐标为pj,qj,可以容纳cj个人。 从大楼i中的人到防空洞j去避难所需的时间为 abs(xi - pi) + (yi - qi) + 1。 现在设计了一个避难计划,指定从大楼i到防空洞j避难的人数 eij。 判断如果按照原计划进行,所有人避难所用的时间总和是不是最小的。 若是,输出“OPETIMAL",若

poj 2135 有流量限制的最小费用最大流

题意: 农场里有n块地,其中约翰的家在1号地,二n号地有个很大的仓库。 农场有M条道路(双向),道路i连接着ai号地和bi号地,长度为ci。 约翰希望按照从家里出发,经过若干块地后到达仓库,然后再返回家中的顺序带朋友参观。 如果要求往返不能经过同一条路两次,求参观路线总长度的最小值。 解析: 如果只考虑去或者回的情况,问题只不过是无向图中两点之间的最短路问题。 但是现在要去要回

poj 3422 有流量限制的最小费用流 反用求最大 + 拆点

题意: 给一个n*n(50 * 50) 的数字迷宫,从左上点开始走,走到右下点。 每次只能往右移一格,或者往下移一格。 每个格子,第一次到达时可以获得格子对应的数字作为奖励,再次到达则没有奖励。 问走k次这个迷宫,最大能获得多少奖励。 解析: 拆点,拿样例来说明: 3 2 1 2 3 0 2 1 1 4 2 3*3的数字迷宫,走两次最大能获得多少奖励。 将每个点拆成两个

poj 2195 bfs+有流量限制的最小费用流

题意: 给一张n * m(100 * 100)的图,图中” . " 代表空地, “ M ” 代表人, “ H ” 代表家。 现在,要你安排每个人从他所在的地方移动到家里,每移动一格的消耗是1,求最小的消耗。 人可以移动到家的那一格但是不进去。 解析: 先用bfs搞出每个M与每个H的距离。 然后就是网络流的建图过程了,先抽象出源点s和汇点t。 令源点与每个人相连,容量为1,费用为

poj 3068 有流量限制的最小费用网络流

题意: m条有向边连接了n个仓库,每条边都有一定费用。 将两种危险品从0运到n-1,除了起点和终点外,危险品不能放在一起,也不能走相同的路径。 求最小的费用是多少。 解析: 抽象出一个源点s一个汇点t,源点与0相连,费用为0,容量为2。 汇点与n - 1相连,费用为0,容量为2。 每条边之间也相连,费用为每条边的费用,容量为1。 建图完毕之后,求一条流量为2的最小费用流就行了

最大流、 最小费用最大流终极版模板

最大流  const int inf = 1000000000 ;const int maxn = 20000 , maxm = 500000 ;struct Edge{int v , f ,next ;Edge(){}Edge(int _v , int _f , int _next):v(_v) ,f(_f),next(_next){}};int sourse , mee

cf 164 C 费用流

给你n个任务,k个机器,n个任务的起始时间,持续时间,完成任务的获利 每个机器可以完成任何一项任务,但是同一时刻只能完成一项任务,一旦某台机器在完成某项任务时,直到任务结束,这台机器都不能去做其他任务 最后问你当获利最大时,应该安排那些机器工作,即输出方案 具体建图方法: 新建源汇S T‘ 对任务按照起始时间s按升序排序 拆点: u 向 u'连一条边 容量为 1 费用为 -c,

【AcWing】851. 求最短路

spfa算法其实是对贝尔曼福特算法做一个优化。 贝尔曼福特算法会遍历所有边来更新,但是每一次迭代的话我不一定每条边都会更新,SPFA是对这个做优化。 如果说dist[b]在当前这次迭代想变小的话,那么一定是dist[a]变小了,只有a变小了,a的后继(b)才会变小。 用宽搜来做优化,用一个队列,队列里边存的就是所有变小了的结点(队列里存的是待更新的点)。 基本思路就是我更新过谁,我再拿

【AcWing】852. spfa判断负环

#include<iostream>#include<algorithm>#include<cstring>#include<queue>using namespace std;const int N= 1e5+10;int n,m;int h[N],w[N],e[N],ne[N],idx;int dist[N],cnt[N];//cnt存最短路径的边数bool st[N];v

蓝桥杯第八届 方格分割(dfs)

标题:方格分割6x6的方格,沿着格子的边线剪开成两部分。要求这两部分的形状完全相同。如图:p1.png, p2.png, p3.png 就是可行的分割法。试计算:包括这3种分法在内,一共有多少种不同的分割方法。注意:旋转对称的属于同一种分割法。请提交该整数,不要填写任何多余的内容或说明文字。   观察可得他是一个中心对称图形,我们只需要搜索它的对称线即可。我们可以把对称线抽象为从(