P4313 文理分科 详细理解

洛谷P4313 同时被 2 个专栏收录
1 篇文章 0 订阅
2 篇文章 0 订阅

P4313 文理分科

网络其他地方看到的原话:在说这道题之前,让我们先思考一下最小割的性质.最小割就是使得s到t割掉的最小边的容量,割过之后,所有的点要么与s联通,要么与t联通.

这样的性质,即要么与s有关系,要么与t有关系的性质(非黑即白)就是典型的最小割的题目.

网络流的割的意思是把整体的点分为两个部分,一个部分是只与S有关联,另一个部分的点只与T有关联,(因为割过之后不可能有一个点既能够与S连也能够与T连,否则的话还是联通的)所以这题的第一步先把每一个点都与S和T分别相连,含义是对于每一个点而言,这个点都可能分配给S或者分配给T其中的一个点, 那么对应的连边的权值就是分配给对应的类别的时候所获得的收益,那么就是在删除的收益最小的前提下把这些中间的点分为两个部分.
在这里插入图片描述
这题还有一个部分是有相邻点之间的关联. **怎么对待呢?**看图得知

在这里插入图片描述

这个新的点,向左链接S,权值为这5个点都为文科时候获得的收益(即周围的几个作为选课与自己一致), 向右链接的5个点的边权都为无穷大.接下来分析这个新的点的作用

假设与之链接的5个点都选择文科(那么他们与T的连线都会从右边与T的连接处切断),那么新的点向左的边就能被保留不需要割断.

同理如果要让T能够与这5个点都有关系,那么首先需要把S和这5个点的链接全部切断,然后新链接的点也必须切断,因为如果不切的话S可以通过新的点到达T,仍然不符合割的定义.

不知道有没有讲清楚:

在这里插入图片描述
举个例子说明一下新的点的作用,比如,对于黑边的切法是这样的(蓝色×表示切除), 那么图中说明1 2 5选择理科,3 4 6选择文科,那么这样一来7号点就必须被切除,否则S就能通过7 再由1 2 5到达T,不满足网络流割,所以这样之下S-7的边必须会被切掉, 这条边的权值正好是这5个点都选文科时候的额外收益, 因为它们被分在了不同的组,所以这个是收益不存在,能够满足题意.切完之后就彻底被分类了.

那么完整的就是对于每一个坐标的文科和相邻都文科时候的收益都建立额外的点,与 S链接权值为对应的收益,与5个点链接权值为无穷大(这样就却不掉了),理科同理 与T链接和5个点链接.

能够理解网络的割在这个题中的体现就可以了,至于具体的怎么切割网络流的算法会实现的,需要做的就是理解网络流图和这个题中各个条件的关联性.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000;
typedef int ll;
const int maxm = 6e5 + 60;
const int inf = 2147483647 / 3;
int cc[5][2] = {{1,0}, {-1, 0}, {0, 1}, {0, -1}, {0,0}};
#define id(i, j) j+m*(i-1)
struct edge {
	int s, d, next;
	ll cap;
};
struct graph {
	edge saveedges[maxm];
	int savecnt = 0;
	int head[maxn*maxn];
	void init() {
		savecnt = 0;
		memset(head, -1, sizeof(head));
	}
	void adde(int s, int d, ll c) {
		saveedges[savecnt].s = s;
		saveedges[savecnt].d = d;
		saveedges[savecnt].next = head[s];
		saveedges[savecnt].cap = c;
		head[s] = savecnt++;

		swap(s, d);
		saveedges[savecnt].s = s;
		saveedges[savecnt].d = d;
		saveedges[savecnt].next = head[s];
		//如果有向图,那么反向边流量为0
		saveedges[savecnt].cap = 0;
		//如果无向图, 反向便流量与正向一致
		//saveedges[savecnt].cap = c;
		head[s] = savecnt++;
	}
}G;

struct isap {
private:
	edge* save_edge;
	int* head;
	int depth[maxn*maxn];
	int depcnt[maxn*maxn];
	void bfs(int t) {
		memset(depth, 0, sizeof(depth));
		memset(depcnt, 0, sizeof(depcnt));
		queue<int>q;
		q.push(t);
		depth[t] = 1;
		depcnt[1] = 1;
		while (!q.empty()) {
			int nowid = q.front(); q.pop();
			for (int noweid = head[nowid]; noweid != -1; noweid = save_edge[noweid].next) {
				edge nowe = save_edge[noweid];
				//如果这条边的终点已经有赋值了,那么就跳过,因为划分层级取每一个点尽可能小的层级
				if (depth[nowe.d])continue;
				depth[nowe.d] = depth[nowid] + 1;
				depcnt[depth[nowe.d]]++;
				q.push(nowe.d);
			}
		}
	}
	/*总的点,用于标识最大的合法层级*/
	ll dfs(int nowpos, ll have, int source, int dest, int N) {
		if (nowpos == dest) {
			return have;
		}
		//have一直不变,flow是会逐步增加的
		ll flow = 0;
		for (int noweid = head[nowpos]; noweid != -1; noweid = save_edge[noweid].next) {
			edge nowe = save_edge[noweid];
			//深度不满足或者这条边的流量没有了,那么跳过这条边的处理
			if (depth[nowe.d] + 1 != depth[nowpos] || nowe.cap == 0)continue;
			//利用当前的边进行递归
			ll newflow = dfs(nowe.d, min(have - flow, nowe.cap), source, dest, N);
			if (newflow) {
				flow += newflow;
				save_edge[noweid].cap -= newflow;
				//用与1异或定位到当前边的反向边
				save_edge[noweid ^ 1].cap += newflow;
			}
			//如果是因为这个点的储存量有限导致不能再从这里向后扩展,那么不改变这个点的层级,直接退出
			if (have == flow) {
				return flow;
			}
		}
		//把当前点向后移动,这一个点所在的层级的节点数少1
		depcnt[depth[nowpos]]--;
		//如果因为这个点后移导致这个层级断开,那么直接设置无法再增广的条件
		if (!depcnt[depth[nowpos]])
			depth[source] = N + 1;
		//层级+1
		depth[nowpos]++;
		//当前层级包括的节点数量+1
		depcnt[depth[nowpos]]++;
		return flow;
	}
public:
	//传入 《图》 结构体,设置用于最大流的前向星的《图》
	void init(graph& g) {
		save_edge = g.saveedges;
		head = g.head;
	}
	ll maxflow(int source, int dest, int N) {
		ll ans = 0;
		//先反向标记层级
		bfs(dest);
		//如果起点的层级能够在最远的范围之内
		while (depth[source] <= N) {
			//就进行dfs
			ll newflow = dfs(source, inf, source, dest, N);
			ans += newflow;
		}
		return ans;
	}
}FLOW;
int n,m;
int saveori[maxn][maxn];
//斜着跳没有记录
int main() {

	// freopen("in.txt", "r", stdin);
	G.init();
	cin>>n>>m;
	int N = n*m;
	int S = 0;
	int T = 3*N+1;
	int ans = 0;

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&saveori[i][j]);
			ans+=saveori[i][j];
			G.adde(S, id(i, j), saveori[i][j]);
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&saveori[i][j]);
			ans+=saveori[i][j];
			G.adde(id(i, j), T, saveori[i][j]);
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&saveori[i][j]);
			ans+=saveori[i][j];
			G.adde(S, id(i, j)+N, saveori[i][j]);
			for(int ccnt = 0;ccnt<5;ccnt++){
				int i1 = i+cc[ccnt][0];
				int j1 = j+cc[ccnt][1];
				if(0<i1 && i1<=n && 0<j1 && j1<=m)
					G.adde(id(i, j)+N, id(i1, j1), inf);
			}
		}
	}

	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&saveori[i][j]);
			ans+=saveori[i][j];
			G.adde(id(i, j)+2*N, T, saveori[i][j]);
			for(int ccnt = 0;ccnt<5;ccnt++){
				int i1 = i+cc[ccnt][0];
				int j1 = j+cc[ccnt][1];
				if(0<i1 && i1<=n && 0<j1 && j1<=m)
					G.adde(id(i1, j1), id(i, j) + N*2, inf);
			}
		}
	}

	FLOW.init(G);
	cout << ans - FLOW.maxflow(S, T, T + 1) << endl;
}

不知道是不是说清了

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

Π鱼星先生

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值