网络流 方格取数+最小割
P2774 方格取数问题
P2774 方格取数问题(https://www.luogu.com.cn/problem/P2774)
题目描述
有一个 mm 行 nn 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。
输入格式
第一行是两个用空格隔开的整数,分别代表方格图的行数 mm 和列数 nn。
第 2 到第 (m + 1)行,每行 n 个整数,第 (i + 1)行的第 j个整数代表方格图第 i 行第 j 列的的方格中的数字 a{i,j} 。
输出格式
输出一行一个整数,代表和最大是多少。
输入输出样例
输入
3 3
1 2 3
3 2 3
2 3 1
输出
11
说明/提示
数据规模与约定
对于 100% 的数据,保证 1≤n,m≤100,1≤a{i,j}≤10^5。
提示
请注意输入的第一行先读入 m 再读入 n。
思路
不难发现,每个方格会与其上下左右四个方格产生矛盾。编程的任务即找到一种不产生矛盾的选择方案,并且使得取出的数总和最大。
首先对图进行黑白染色,目的是使产生矛盾的两个位置分别位于不同的色块中,方便建图。
源点与所有白色位置相连,权值为该位置上的数字;所有黑色位置与汇点相连,权值也为该位置上的数字;所有白色位置与其上下左右(注意边界情况)的黑色位置相连,权值为无穷大。
如此建图后,可以发现存在源点到汇点的增广路,这也意味着原图中存在产生矛盾的两个位置。假设一开始选取M*N网格中的所有方块,我们的任务是割掉网络中的一些边(即删去一些方块),使得割去的边权最小。割去网络中的边就相当于删掉两个矛盾位置中的其中一个,因此当网络中不再有源点到汇点的增广路,就意味着矛盾全部消除。
问题便转化为求解最小割(最大流)的问题。输出答案为全局和减去最小割。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 4000 + 10;
const int INF = 0x3f3f3f3f;
//注释为弧优化
struct node {
int form, to, cap, flow, next;
} edge[2000006];
int head[maxn];
int cnt;
struct max_Folw {
int d[maxn], cur[maxn], start, tend;
bool vis[maxn];
void init(int s, int t) {
memset(head, -1, sizeof(head));
cnt=0;
start=s, tend=t;
}
void add(int start, int to, int cap) {
edge[cnt].form = start;
edge[cnt].to = to;
edge[cnt].cap = cap;
edge[cnt].flow = 0;
edge[cnt].next = head[start];
head[start] = cnt++;
}
void AddEdge(int start, int to, int cap) {
add(start, to, cap);
add(to, start, 0);
}
bool BFS() {
memset(d, -1, sizeof(d));
int Q[maxn * 2];
int Thead, Ttail;
Thead = Ttail = 0;
Q[Ttail++] = tend;
d[tend] = 0;
while (Thead<Ttail) {
int x = Q[Thead];
if (x == start)
return true;
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == -1 && edge[i^1].cap > edge[i^1].flow) { //没有标记,且可行流大于0
d[temp] = d[x] + 1;
Q[Ttail++] = temp;
}
}
Thead++;
}
return false;//汇点是否成功标号,也就是说是否找到增广路
}
int DFS(int x, int cap) {
if (x == tend)
return cap;
int flow = 0, f;
//for (int i = cur[x]; i != -1; i = edge[cur[x]=i].next) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int temp = edge[i].to;
if (d[temp] == d[x] - 1 && edge[i].cap > edge[i].flow) {
f = DFS(temp, min(cap - flow, edge[i].cap - edge[i].flow));
edge[i].flow += f;
edge[i ^ 1].flow -= f;
flow += f;
if (flow == cap)
return flow;
}
}
d[x] = -2;//防止重搜
return flow;
}
int maxflow() {
int flow = 0, f;
while (BFS()) {
//memcpy(cur, head, sizeof head);
flow += DFS(start, INF);
}
return flow;
}
} flow;
int a[105][105];
int xx[4]={0, 0, 1, -1};
int yy[4]={1, -1, 0, 0};
int n, m;
void Add(int i, int j){
for(int k=0; k<4; k++){
int x=xx[k]+i, y=yy[k]+j;
if(x<=n&&x>=1&&y>=1&&y<=m){
flow.AddEdge((i-1)*m+j, (x-1)*m+y, INF);
}
}
}
int main(){
scanf("%d%d", &n, &m);
int pos0=0, pos1=0;
flow.init(0, n*m+1);
int ans=0;
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
int x; scanf("%d", &a[i][j]);
ans+=a[i][j];
if((i+j)%2){
flow.AddEdge(0, (i-1)*m+j, a[i][j]);
}
else{
flow.AddEdge((i-1)*m+j, n*m+1, a[i][j]);
}
}
}
for(int i=1; i<=n; i++){
for(int j=1; j<=m; j++){
if((i+j)%2){
Add(i, j);
}
}
}
printf("%d\n", ans-flow.maxflow());
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)