【学习笔记】图论建模(2-SAT,网络流)

2-SAT相关

板子

基本 2-SAT

代码
inline int calc(int x,int y){ return y ? x + n : x; }
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1,x,a,y,b;i<=m;++i){
		scanf("%d%d%d%d",&x,&a,&y,&b);
		G.add(calc(x,a^1),calc(y,b));
		G.add(calc(y,b^1),calc(x,a));
	}
	for(int i = 1;i<=n*2;++i)if(!dfn[i])tarjan(i);
	for(int i = 1;i<=n;++i)
		if(bt[i] == bt[i+n])return puts("IMPOSSIBLE"),0;
	puts("POSSIBLE");
	for(int i = 1;i<=n;++i)
		printf(bt[i] < bt[i+n] ? "0 ":"1 ");
	return 0;
}

2-SAT 前缀优化建图

代码
inline int id(int x,bool yes){ return yes ? x : x + n; }
inline int calc(int x,bool yes){ return yes ? x + 2*n : x + 3*n; }
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i = 1,u,v;i<=m;++i){
		scanf("%d%d",&u,&v);
		G.add(id(u,0),id(v,1)),G.add(id(v,0),id(u,1));
	}
	for(int i = 1,w;i<=k;++i){
		scanf("%d",&w);
		for(int j = 1;j<=w;++j){
			scanf("%d",&a[j]);
			G.add(id(a[j],1),calc(a[j],1)),G.add(calc(a[j],0),id(a[j],0));
			if(j ^ 1){
				G.add(calc(a[j-1],1),calc(a[j],1)),G.add(calc(a[j],0),calc(a[j-1],0));
				G.add(calc(a[j-1],1),id(a[j],0)),G.add(id(a[j],1),calc(a[j-1],0));
			}
		}
	}
	for(int i = 1;i<=4*n;++i)if(!dfn[i])tarjan(i);
	for(int i = 1;i<=n;++i)
		if(bt[id(i,0)] == bt[id(i,1)] || bt[calc(i,0)] == bt[calc(i,1)])return puts("NIE"),0;
	puts("TAK");
	return 0;
}
/*
pre_i 表示该部分中前 i 个点是否有点被选为关键点
a_i -> pre_i, !pre_i -> !a_i
pre_{i-1} -> pre_{i}  !pre_{i} -> !pre_{i-1}
pre_{i-1} -> !a_i    a_i -> !pre_{i-1}
*/

2-SAT 线段树优化建图

代码
struct Graph{
	int head[N<<3],etot;
	struct node{
		int nxt,v;
	}edge[M];
	void init(){ memset(head,0,sizeof head); etot = 0; }
	void add(int x,int y){
		edge[++etot] = {head[x],y};
		head[x] = etot;
	}
	node & operator [](const int i){ return edge[i]; }
}G;
inline int trans(int x){ return x > n ? x - n : x + n; }
struct Seg{
	struct node{
		int l,r;
	}tr[N<<2];
	#define ls (p<<1)
	#define rs (p<<1|1)
	#define mid ((tr[p].l+tr[p].r)>>1)
	void build(int p,int l,int r){
		tr[p] = {l,r};
		id[p] = ++tot;
		if(l == r)return G.add(id[p],trans(s[l].id)),void();
		build(ls,l,mid),build(rs,mid+1,r);
		G.add(id[p],id[ls]),G.add(id[p],id[rs]);
	}
	void modify(int p,int l,int r,int v){
		if(l <= tr[p].l && tr[p].r <= r)return G.add(v,id[p]),void();
		if(l <= mid)modify(ls,l,r,v);
		if(mid < r)modify(rs,l,r,v);
	}
	#undef mid
}seg;
inline bool check(int lim){
	tim = scc_cnt = top = 0;
	G.init();
	memset(dfn,0,sizeof dfn);
	seg.build(1,1,tot = n<<1);
	for(int i = 1;i<=n*2;++i){
		int l = upper_bound(s+1,s+n*2+1,P{s[i].pos-lim,0})-s;
		int r = upper_bound(s+1,s+n*2+1,P{s[i].pos+lim-1,0})-s-1;
		seg.modify(1,l,i-1,s[i].id),seg.modify(1,i+1,r,s[i].id);
	}
	for(int i = 1;i<=n*2;++i)if(!dfn[i])tarjan(i);
	for(int i = 1;i<=n;++i)
		if(bt[i] == bt[i+n])return false;
	return true;
}

例题

P3007 [USACO11JAN] The Continental Cowngress G

这题着实加深了我对 2-SAT 的理解。
在 tarjan 缩点并判断是否有解后,里面的 \(O(n^2)\)dfs,即从某个状态开始,往下面跑,如果正反两个点都被跑到则说明这个状态不合法。我们 2-SAT 所建成的有向图本质上是阐释不同状态之间的关系,而状态的正确与否。

竞赛图分出三个点集

处理点之间的bool关系,使用2-SAT

\(A\to B\to C\to A\)。那么 \(A,B,C\) 的先后是无关紧要的,可以钦定 \(1\)\(A\)。由于是竞赛图,对与那些可能为 \(B\) 中的元素 \((1,x)\in E\),和可能为 \(C\) 的元素 \((x,1)\in E\),处理,令其为 \(B',C'\)

  1. \(x \in B, y \in B\),若 \((x,y) \in E\)\(x\) 属于 \(B\)\(y\) 一定属于 \(B\);若 \((x,y) \notin E\)\(x\) 属于 \(A\)\(y\) 一定属于 \(A\)
  2. \(C\) 的内部同理。
  3. \(x \in B,y \in C\),若 \((x,y) \in E\),那么 \(x,y\) 要么同属于 \(A\),要么同不属于 \(A\);否则 \(x \notin A \to y \in A,y \notin A \to x \in A\)

题面还有一个要求,要求三个集合都不为空,那么就要钦定一些点作为 \(B,C\) 中的点。

对于 \(B\) 中的点,考虑 \(B'\) 所在的子图 \(G'\)。若存在一个点 \(x_0 \in B',x_0 \in A\),那么若 \((y_0,x_0) \in E\),就有 \(y_0 \in A\)。就是找到 \(B'\) 中所有点都可以到达的点 \(x\)(竞赛图缩点后是一条链)。\(C\) 同样是,就是找到可以从 \(x_0\) 出发到达 \(C'\) 中所有点的 \(x_0\)

代码

#include<bits/stdc++.h>
using namespace std;
constexpr int S1=1<<20;
char buf1[S1],*l1,*r1;
#define getchar() ((l1==r1&&(r1=(l1=buf1)+fread(buf1,1,S1,stdin)),l1!=r1)?*l1++:EOF)
template<typename T=int>inline T read()
{
	T x=0;
	char c=getchar();
	while(c<'0'||c>'9')
		c=getchar();
	while(c>='0'&&c<='9')
	{
		x=c-'0'+x*10;
		c=getchar();
	}
	return x;
}
inline void end(){
	puts("0 0 0");
	exit(0);
}
const int N = 5010;
vector<int> e[N<<1];
int dfn[N << 1],low[N << 1],tim;
int st[N << 1],top,scc_cnt;
bool inst[N << 1];
int bt[N << 1],n;
vector<int> B,C;
bool g[N][N];
void tarjan(int u){
	low[u] = dfn[u] = ++tim;
	st[++top] = u;
	inst[u] = 1;
	for(int v : e[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u] = min(low[u],low[v]);
		}else if(inst[v])low[u] = min(low[u],dfn[v]);
	}
	if(dfn[u] == low[u]){
		++scc_cnt;
		int z;
		do{
			z = st[top--];
			inst[z] = 0;
			bt[z] = scc_cnt;
		}while(z ^ u);
	}
}
int main(){
	freopen("b6e7.in","r",stdin);
	freopen("b6e7.out","w",stdout);
	n = read();
	for(int i = 1;i<=n;++i)for(int j = 1;j<=n;++j)g[i][j] = read();
	for(int i = 2;i<=n;++i)
		if(g[1][i])B.push_back(i);
		else C.push_back(i);
	if(B.empty() || C.empty())end();
	e[n+1].push_back(1);
	for(int x : B)for(int y : B){
		if(x == y)continue;
		if(g[x][y])e[x+n].push_back(y+n);
		else e[x].push_back(y);
	}
	for(int x : C)for(int y : C){
		if(x == y)continue;
		if(g[x][y])e[x].push_back(y);
		else e[x+n].push_back(y+n);
	}
	for(int x : B)for(int y : C)
		if(g[x][y]){
			e[x].push_back(y);
			e[x+n].push_back(y+n);
			e[y].push_back(x),e[y+n].push_back(x+n);
		}else{
			e[x+n].push_back(y);
			e[y+n].push_back(x);
		}
	int x = B[0];
	while(1){
		bool flag = 1;
		inst[x] = 1;
		for(int y : B)if(!inst[y] && g[x][y]){
			x = y, flag = 0;
			break;
		}
		if(flag)break;
	}
	e[x].push_back(x+n);
	x = C[0];
	while(1){
		bool flag = 1;
		inst[x] = 1;
		for(int y : C)if(!inst[y] && g[y][x]){
			x = y, flag = 0;
			break;
		}
		if(flag)break;
	}
	e[x].push_back(x+n);
	memset(inst,0,sizeof inst);
	for(int i = 1;i<=n*2;++i)if(!dfn[i])tarjan(i);
	for(int i = 1;i<=n;++i)if(bt[i] == bt[i+n])end();
	int siza = 0, sizb = 0, sizc = 0;
	for(int i = 1;i<=n;++i)if(bt[i] < bt[i+n])++siza;
	for(int x : B)if(bt[x+n] < bt[x])++sizb;
	for(int x : C)if(bt[x+n] < bt[x])++sizc;
	printf("%d %d %d\n",siza,sizb,sizc);
	for(int i = 1;i<=n;++i)if(bt[i] < bt[i+n])printf("%d ",i); puts("");
	for(int x : B)if(bt[x+n] < bt[x])printf("%d ",x); puts("");
	for(int x : C)if(bt[x+n] < bt[x])printf("%d ",x); puts("");
	return 0;
}

网络流

EK:每次在残图上 bfs 出到 t 最短的一条路径,update  O(n m^2)
Dinic:每次 bfs 出分层图,再 dfs 增广 O(n^2 m)
费用流:EK 算法前用 spfa 跑出残余图上最小 cost 的一条路径 

处理二分图最大匹配:O(n sqrt(m))
P2756 飞行员配对方案问题
方案看剩余流量 w 是否为 0 表示是否选择这了这个匹配 

最大流:
①边权用于限制次数(往往是血量、次数上限、拆点) P3701 主主树
②模拟实物的空间变化 P2472 [SCOI2007] 蜥蜴
③处理每个数有选择次数限制时,拆点,每个右部点都可以连向 t,既如果选择结束,就不会对其它地方产生贡献。 P2766 最长不下降子序列问题 
 
拆点:
①经过一个点次数有限定,将该点拆开
②用于防止重复 P1231 教辅的组成 
②路径首尾相连,n 个点拆为二分图,可以跑 DAG 上最小路径覆盖 
	P2764 最小路径覆盖问题 
	最小路径覆盖 = 点的总数 - 最大流(最大匹配) 
	分析:对于一条点数为 len 的覆盖路径,贡献的流为 len-1
	路径条数 = (n - 1) - \sum (len - 1) + 1 = n - \sum (len - 1)
	方案用 DSU 
最小割:
①割的本意:分割两个集合(分别与 s 和 t 相连)P2598 [ZJOI2009] 狼和羊的故事 、P5934 [清华集训 2012] 最小生成树、P5934 [清华集训 2012] 最小生成树 
②用于选择(割出/选出 两个集合) 
	P2774 方格取数问题 
	对黑白点之间
	由于互斥的点之间的边权是 inf,一定不会割中间,会割 s->黑 和 白->t(即以该点数的大小为代价放弃某个数)  
	P4076 [SDOI2016] 墙上的句子
③巧妙的构造:P1646 [国家集训队] happiness(https://i.loli.net/2018/09/01/5b8a22633625a.png)

平面图最小割 = 对偶图最短路
P4001 [ICPC-Beijing 2006] 狼抓兔子
「NOI2010」海拔 


[国家集训队] happiness
S->a 
 \ || \
   b-> T

P5934 [清华集训 2012] 最小生成树

在最小生成树上:
w < L 的连了,要保证 u, v 不联通
w > L 的连了,要保证 u, v 不联通
不连通+最小花费->最小割解决


P5039 [SHOI2010] 最小生成树
一定在=>将 w = L 的加入讨论 
 
P1251 餐巾计划问题
费用流
拆为 早上 和 晚上


P6061 [加油武汉] 疫情调查
用环来覆盖一张图的最小代价
拆点后跑最小路径覆盖
用费用流

最大流

板子

EK:

代码
bool bfs(){
	memset(vis,0,sizeof vis);
	queue<int> q;
	q.push(s), vis[s] = 1;
	rest[s] = inf;
	while(!q.empty()){
		int x = q.front(); q.pop();
		for(int i = G.head[x];i;i = G[i].nxt)if(G[i].w){
			int v = G[i].v;
			if(vis[v])continue;
			rest[v] = min(rest[x], G[i].w);
			pre[v] = i, q.push(v), vis[v] = 1;
			if(v == t)return 1;
		}
	}
	return 0;
}
void update(){
	int now = t;
	while(now != s){
		int i = pre[now];
		G[i].w -= rest[t];
		G[i^1].w += rest[t];
		x = G[i^1].v;
	}
	mxflow += rest[t];
}
while(bfs())update();

Dinic:

代码
    namespace Net{
	int S,T;
	int head[510],work[510],etot = 1;
	struct node{ int nxt,v,cap; }edge[160010];
	inline void add(int x,int y,int w){
		edge[++etot] = {head[x],y,w};
		head[x] = etot;
	}
	inline void addedge(int u,int v,int w){ add(u,v,w),add(v,u,0); }
	int dis[510];
	bool vis[510];
	bool bfs(){
		queue<int> q;
		for(int i = S;i<=T;++i)dis[i] = -1;
		dis[S] = 0;
		q.push(S);
		while(!q.empty()){
			int x = q.front(); q.pop();
			for(int i = head[x];i;i = edge[i].nxt){
				int v = edge[i].v;
				if(edge[i].cap > 0 && dis[v] == -1){
					dis[v] = dis[x] + 1;
					q.push(v);
				}
			}
		}
		return (dis[T] >= 0);
	}	
	int dfs(int u,int flow){
		if(u == T)return flow;
		for(int &i = work[u];i;i = edge[i].nxt){
			int v = edge[i].v;
			if(edge[i].cap > 0 && dis[v] == dis[u] + 1){
				int tmp = dfs(v,min(flow,edge[i].cap));
				if(tmp > 0){
					edge[i].cap -= tmp;
					edge[i^1].cap += tmp;
					return tmp;
				}
			}
		}
		return 0;
	}
	int Dinic(){
		int ans = 0;
		while(bfs()){
			for(int i = S;i<=T;++i)work[i] = head[i];
			while(1){
				int flow = dfs(S,inf);
				if(flow == 0)break;
				ans += flow;
			}
		}
		return ans;
	}
}

Dinic 邻接矩阵(没有当前弧优化)

代码
namespace Net{
	int S,T;
	ll cap[N][N];
	inline void addedge(int x,int y,ll w){
		cap[x][y] = w;
		cap[y][x] = 0;
	}
	int dist[N];
	bool vis[N];	
	ll dfs(int u,ll flow){
		if(u == T)return flow;
		for(int i = S;i<=T;++i)if(cap[u][i] != -1){
			if(cap[u][i] > 0 && dist[i] == dist[u] + 1){
				int tmp = dfs(i,min(cap[u][i],flow));
				if(tmp > 0){
					cap[u][i] -= tmp;
					cap[i][u] += tmp;
					return tmp;
				}
			}
		}
		return 0;
	}
	bool bfs(){
		queue<int> q;
		for(int i = S;i<=T;++i)dist[i] = -1;
		q.push(S); dist[S] = 0;
		while(!q.empty()){
			int x = q.front(); q.pop();
			for(int i = S;i<=T;++i)if(cap[x][i] > 0 && dist[i] == -1){
				dist[i] = dist[x] + 1;
				q.push(i);
			}
		}
		return (dist[T] >= 0);
	}
	ll Dinic(ll lim){
		S = 0, T = F * 2 + 1;
		for(int i = S;i<=T;++i)
			for(int j = S;j<=T;++j)cap[i][j] = -1;	
		for(int i = 1;i<=F;++i){
			addedge(S,i,cow[i]);
			addedge(i+F,T,pen[i]);
			for(int j = 1;j<=F;++j)
				if(dis[i][j] <= lim)addedge(i,j+F,inf);
		}
		ll ans = 0;
		while(bfs()){
			while(1){
				ll flow = dfs(S,inf);
				if(flow == 0)break;
				ans += flow;
			}
		}
		return ans;
	}
}
using namespace Net;

例题:

P3254圆桌问题

basic

套路地,将人看作“流水”,那么我们可以通过人数的流向具象化桌子与单位之间的关系。具体做法:

每一个单位作为一个点,每一个桌子看作一个点,源点 \(S\) 向每一个单位 \(i\) 连一条权值为 \(r[i]\) 的边,每一个桌子 \(i\) 向汇点连一条权值为 \(c[i]\) 的边。这样我们看所有人是否可以都入座就是看最大流是不是等于总人数。

对于中间的,由于题目条件“同一个单位来的代表不在同一个餐桌就餐”,因此,每一个单位向每一个餐桌连一条权值为 \(1\) 的有向边。

方案就是去看看对于一条 单位->桌子 的边,跑完后这条边的剩余容量是否为 \(0\),若是,则表明这一条边有人选择。

核心代码
scanf("%d%d",&m,&n);
S = 0, T = m + n + 1;
for(int i = 1;i<=m;++i){
	scanf("%d",&r[i]);
	addedge(S,i,r[i]);
	cnt += r[i];
}
for(int i = 1;i<=n;++i){
	scanf("%d",&c[i]);
	addedge(i+m,T,c[i]);
}
for(int i = 1;i<=m;++i)
	for(int j = m+1;j<=m+n;++j)
		addedge(i,j,1);
int res = Dinic();
if(res != cnt)return puts("0"),0;
puts("1");
for(int i = 1;i<=m;++i){
	for(int j = head[i];j;j = edge[j].nxt){
		int v = edge[j].v - m;
		if(edge[j].cap == 0)printf("%d ",v);
	}
	puts("");
} 

P6768 [USACO05MAR] Ombrophobic Bovines 发抖的牛

二分

要求最小时间,明显符合单调性,故二分答案。

与上题类似,将牛作为“水流”。

建图:

  1. S->每一个田地(代表这个田地的牛),边权为该位置牛的数量
  2. 每一个田地(代表这个田地的牛棚)->T,边权为牛棚容量
  3. 对于在二分的 \(mid\) 时间内可以到达的位置,连出一条权值为 \(inf\) 的边,因为“路很宽,无限量的牛可以通过”。

观察到数据范围很小,最短路可以用 Floyed 处理。

核心代码
ll Dinic(ll lim){
	S = 0, T = F * 2 + 1;
	for(int i = S;i<=T;++i)
		for(int j = S;j<=T;++j)cap[i][j] = -1;
	for(int i = 1;i<=F;++i){
		addedge(S,i,cow[i]);
		addedge(i+F,T,pen[i]);
		for(int j = 1;j<=F;++j)
			if(dis[i][j] <= lim)addedge(i,j+F,inf);
	}
	ll ans = 0;
	while(bfs()){
		while(1){
			ll flow = dfs(S,inf);
			if(flow == 0)break;
			ans += flow;
		}
	}
	return ans;
}

P2891 [USACO07OPEN] Dining G

拆点

由于“每头牛只享用一种食物和一种饮料”,我们直接 S->食物->牛->饮料->T 的思路是错误的。

那么我们应当利用网络流中边的限定功能。

考虑拆点,一只牛被拆成了两个点,两点间连了一条权值为 \(1\) 的边,这样就可以保证一只牛只吃一对了。

拆点是个好东西!

建图代码
S = 0, T = 2 * n + F + D + 1;
for(int i = 1;i<=F;++i)addedge(S,i,1);
for(int i = 2*n+F+1;i<=2*n+F+D;++i)addedge(i,T,1);
for(int i = 1;i<=n;++i)addedge(F+i,F+i+n,1);
for(int i = 1,a,b;i<=n;++i){
	scanf("%d%d",&a,&b);
	// cow_i F+i->F+i+n
	for(int j = 1,x;j<=a;++j){
		scanf("%d",&x);
		addedge(x,F+i,1);
	}
	for(int j = 1,x;j<=b;++j){
		scanf("%d",&x);
		addedge(F+i+n,2*n+F+x,1);
	}
}
printf("%d",Dinic());

P3191 [HNOI2007] 紧急疏散EVACUATE

二分 + 拆点

二分时间。

拆点,将每一扇门按照时间都拆成 \(mid\) 扇。

如果按照最为朴素的建图方式,边会存不下,因为我们要按照一个空位->[到达该门的时间,mid]的门去建边。

一个 \(trick\) 就是对于一扇门所拆成的几扇门,\(i\)\(i+1\) 连一条权值为 \(inf\) 的边,这样就可以使得“等待”的过程得到实现。

完整代码
#include<bits/stdc++.h>
#define print(a) cout << #a"=" << a << endl
#define debug() cout << "Line:" << __LINE__ << endl
#define sign() puts("----------")
using namespace std;
typedef pair<int,int> pii;
const int inf = 0x3f3f3f3f;
int n,m;

const int N = 1e5 + 10; 
void build(int mid);
namespace Net{
	int S,T;
	int head[N],work[N],etot = 1;
	struct node{ int nxt,v,cap; }edge[N<<1];
	inline void add(int x,int y,int w){
		edge[++etot] = {head[x],y,w};
		head[x] = etot;
	}
	inline void addedge(int u,int v,int w){ add(u,v,w),add(v,u,0); }
	int dis[N];
	bool vis[N];
	inline void init(){
		memset(head,0,sizeof head);
		etot = 1;
	}
	bool bfs(){
		queue<int> q;
		for(int i = S;i<=T;++i)dis[i] = -1;
		dis[S] = 0;
		q.push(S);
		while(!q.empty()){
			int x = q.front(); q.pop();
			for(int i = head[x];i;i = edge[i].nxt){
				int v = edge[i].v;
				if(edge[i].cap > 0 && dis[v] == -1){
					dis[v] = dis[x] + 1;
					q.push(v);
				}
			}
		}
		return (dis[T] >= 0);
	}	
	int dfs(int u,int flow){
		if(u == T)return flow;
		for(int &i = work[u];i;i = edge[i].nxt){
			int v = edge[i].v;
			if(edge[i].cap > 0 && dis[v] == dis[u] + 1){
				int tmp = dfs(v,min(flow,edge[i].cap));
				if(tmp > 0){
					edge[i].cap -= tmp;
					edge[i^1].cap += tmp;
					return tmp;
				}
			}
		}
		return 0;
	}
	int Dinic(int mid){
		build(mid);
		int ans = 0;
		while(bfs()){
			for(int i = S;i<=T;++i)work[i] = head[i];
			while(1){
				int flow = dfs(S,inf);
				if(flow == 0)break;
				ans += flow;
			}
		}
		return ans;
	}
}
using namespace Net;

int tot;

char s[25][25];
bool mark[25][25];
int id[25][25];

struct P{ int x,y,d; };

const int step[4][2] = {{1,0},{0,1},{-1,0},{0,-1}};
void bfs(int sx,int sy,int mid,int st){
	memset(mark,0,sizeof mark);
	queue<P> q;
	q.push({sx,sy,0});
	mark[sx][sy] = 1;
	while(!q.empty()){
		int x = q.front().x, y = q.front().y,d = q.front().d; q.pop();
		if(d > mid)continue;
		if(s[x][y] == '.')addedge(id[x][y],st+d,1);
		for(int i = 0;i<4;++i){
			int xx = x + step[i][0], yy = y + step[i][1];
			if(xx < 1 || xx > n || yy < 1 || yy > m)continue;
			if(mark[xx][yy] || s[xx][yy] == 'X' || s[xx][yy] == 'D')continue;
			q.push({xx,yy,d+1});
			mark[xx][yy] = 1;
		}
	}
}



vector<pii> v;
void build(int mid){
	init();
	int point = v.size();
	S = 0, T = tot + point * mid + 1;
	/*
	1~P ren
	P+1 ~ P + point * mid
	*/
	for(int i = 1;i<=tot;++i)addedge(S,i,1);
	for(int i = tot+1;i<=tot+point*mid;++i)addedge(i,T,1);
	for(int i = 0;i<point;++i){
		// P+i*mid + [1,mid]
		for(int j = tot+i*mid+1;j<tot+(i+1)*mid;++j)addedge(j,j+1,inf);
	}
	
	for(int i = 0;i<point;++i)
		bfs(v[i].first,v[i].second,mid,tot+i*mid);
	
}


int sum;
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=n;++i)
		scanf("%s",s[i]+1);
	for(int i = 1;i<=n;++i)
		for(int j = 1;j<=m;++j)
			if(s[i][j] == 'D')v.push_back({i,j});
			else if(s[i][j] == '.')id[i][j] = ++tot,++sum;
	int l = 1, r = 1000, res = -1;
	while(l <= r){
		int mid = (l + r) >> 1;
		if(Dinic(mid) == sum){
			res = mid;
			r = mid - 1;
		}else l = mid + 1;
	}
	if(res == -1)puts("impossible");
	else printf("%d",res);
	return 0;
}

总结

我们构造网络流的过程就是一个将题意转化为具体的的图,并将其丢给网络流解决的过程。

我们并不需要过多地去关注过程中的决策,而是将那些限制转化到图上即可。

这便是建模的魅力所在。

posted @ 2025-03-27 19:06  Luzexxi  阅读(80)  评论(0)    收藏  举报