ABC205F - Grid and Tokens——网络流经典模型

这篇博客介绍了一种使用网络流算法解决棋子放置问题的方法。在H×W的网格中,有n颗棋子,每颗棋子有特定的可放置范围,要求不在同一行或同一列。博主通过构建网络流模型,将棋子拆分为行和列的选择点,并建立源点、汇点以及棋子与行、列之间的连接,最终通过运行最大流算法求解能放置的最大棋子数。代码中展示了C++实现的最大流算法 Dinic 算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

F - Grid and Tokens

题目描述

n n n 颗棋子欲放到 H ∗ W H*W HW 的网格中,每颗棋子可放的范围是一个矩阵,且不能有两颗棋子待在同一行或同一列,求最多可以放多少颗棋子。

数据范围与提示

1 ≤ n , H , W ≤ 100 1\le n,H,W\le 100 1n,H,W100

前言

考试时半个小时内就A完了前五题,以为终于可以像 O n e I n D a r k \rm OneInDark OneInDark 一样尝尝AK的感觉(即使是ABC),结果最后一题硬是没建出来。

这题的做法应该是 网络流24题 里面出现过的,不然不会有这么多人选择先做F再做E。可惜我当初没好好学(也许是为了赶进度),网络流只做了两三道练习题就不管了,懈怠了这么强大的算法。

思路

先分析一下结构:

  • 每个棋子要恰好选范围内的一行,再选范围内的一列;
  • 每一行只能被选一次;
  • 每一列只能被选一次。

显然棋子是夹在匹配关系的中间的,所以把每个棋子拆成两个点 U i U_i Ui V i V_i Vi ,建 H H H 个点 R 1 ∼ H R_{1\sim H} R1H 表示行,建 W W W 个点 C 1 ∼ W C_{1\sim W} C1W 表示列;

官方题解的图
(骠一张官方题解的图)

从原点向每个 R i R_i Ri 连一条边(每条边容量都为1,后面也一样),从每个 C i C_i Ci 向汇点连一条边,每个 U i U_i Ui V i V_i Vi 连一条边, R a i ∼ c i R_{a_i\sim c_i} Raici 分别向 U i U_i Ui 连一条边, V i V_i Vi C b i ∼ d i C_{b_i\sim d_i} Cbidi 连一条边。

跑最大流即可。

代码

#include<cstdio>//JZM YYDS!!!
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<ctime>
#define ll long long
#define MAXN 1005
#define MAXM 100005
#define uns unsigned
#define MOD 998244353ll
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&(-(x)))
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,m,p,A,B,C,D,E;
 
int f[MAXM],d[MAXN],cur[MAXN],IN;
struct edge{
	int v,id;edge(){}
	edge(int V,int I){v=V,id=I;}
};
vector<edge>G[MAXN];
inline void addedge(int u,int v,int w){
	G[u].push_back(edge(v,IN)),G[v].push_back(edge(u,IN^1));
	f[IN]=w,f[IN^1]=0,IN+=2;
}
queue<int>q;
inline bool bfs(int S,int T){
	memset(d,-1,sizeof(d));
	while(!q.empty())q.pop();
	d[S]=0,q.push(S);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(uns i=0;i<G[u].size();i++){
			int v=G[u][i].v,a=G[u][i].id;
			if(f[a]>0&&d[v]<0)d[v]=d[u]+1,q.push(v);
		}
	}
	return d[T]>=0;
}
inline int dfs(int x,int T,int lim){
	int res=lim;
	if(x==T)return lim;
	for(uns i=cur[x];i<G[x].size()&&res;i++){
		cur[x]=i;
		int v=G[x][i].v,a=G[x][i].id;
		if(f[a]>0&&d[v]==d[x]+1){
			int ad=dfs(v,T,min(res,f[a]));
			res-=ad,f[a]-=ad,f[a^1]+=ad;
		}
	}
	return lim-res;
}
inline int dinic(int S,int T){
	int res=0;
	while(bfs(S,T)){
		memset(cur,0,sizeof(cur));
		while(int ad=dfs(S,T,INF))res+=ad;
	}
	return res;
}
signed main()
{
	n=read(),m=read(),p=read();
	A=1,B=A+p,C=B+p,D=C+n,E=D+m+1;
	for(int i=1;i<=n;i++)addedge(A,C+i,1);
	for(int i=1;i<=m;i++)addedge(D+i,E,1);
	for(int i=1;i<=p;i++){
		addedge(A+i,B+i,1);
		int a=read(),b=read(),c=read(),d=read();
		for(int j=a;j<=c;j++)addedge(C+j,A+i,1);
		for(int j=b;j<=d;j++)addedge(B+i,D+j,1);
	}
	printf("%d\n",dinic(A,E));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值