矩阵图一定是一个二分图,问题转化成在这个二分图上有 k 条边,要满足任意一条边两端的点权和大于这条边的边权,求最小的点权和。
在这个二分图上,考虑只给 X 部的点赋点权,每一个 X 部的点的点权等于连出去的边的边权的最大值, Y 部的点的点权都是 0。
接下来考虑升高一部分 Y 部的点的点权,使得更多的 X 部的点的点权下降。
例如:
此时若 Y 升高 d,则右边的 X 都可以减少 d,最终答案变小。
在更复杂的图里难以判断升高某个点的点权是否会使得答案减小,也可以会使得答案增大。
考虑什么情况下可以升高 Y 点的点权:
如果了解 KM 算法,这个模型实际上就是 KM 算法的模型,每个点的点权就是顶标。
KM 算法寻找增广路的方法和 匈牙利算法相同,访问的边的条件多了一条
v
a
l
[
x
]
+
v
a
l
[
y
]
=
=
w
val[x] + val[y] == w
val[x]+val[y]==w。
因此每一次寻找增广路是在原图中的一个子图中寻找,当某个点无法找到匹配点时扩充子图。
如何扩充子图: 通过修改顶标,使得一些权值更小的边也被考虑进来,就完成了子图扩充。
如何修改顶标:
在寻找增广路时 dfs 遍历会得到一棵匹配边和非匹配边交替的交替树(似乎也叫匈牙利树),将交替树中的 X 部的点集的顶标下降 d,Y 部的点集的顶标升高 d,d 是 x 在交替树上的X集,而边不在交替树上的边的
v
a
l
[
u
]
+
v
a
l
[
v
]
−
w
val[u] + val[v] - w
val[u]+val[v]−w 的最小值。
在KM算法中,当无法找到增广路时,所有顶点标号之和会降低: 原因是当找不到增广路时,交替树中X 部的点集大小 s i z e x size_x sizex > Y 部的点集大小 s i z e y size_y sizey( s i z e x = s i z e y + 1 size_x = size_y + 1 sizex=sizey+1)。因为交替树中所有 Y 部的点都是已匹配点,对应就有相同数量的 X部 的 点,而当前正在寻找匹配点的 x 就是多出来的那个点。
在求二分图最大权值匹配中,所有顶点的顶标和只在找不到增广路时下降,当整个二分图求得最大权值匹配时,顶标和达到最小值(无法继续下降)。而顶标和的 = 最大权值匹配
求最大权值匹配,可以用费用流(也可以用 KM 算法,KM算法求的是 最大权值完备匹配,这题对所有点建图不一定存在 完备匹配,只对输入边建图一定存在完备匹配但需要处理一下,而费用流可以直接求最大权值匹配,相对来说处理更方便)
具体见代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5;
#define pii pair<int,int>
#define fir first
#define sec second
typedef long long ll;
const int inf = 0x3f3f3f3f;
int n,m,k;
set<int> tmp;
struct ss{
int u,v,c,w,nxt;
}edg[maxn];
int head[maxn],cnt,vis[maxn],pre[maxn],d[maxn];
void init() {
cnt = 0;
memset(head,-1,sizeof head);
}
void add(int u,int v,int c,int w) {
edg[cnt].u = u;
edg[cnt].v = v;
edg[cnt].c = c;
edg[cnt].w = w;
edg[cnt].nxt = head[u];
head[u] = cnt++;
}
bool spfa(int s,int t) {
queue<int> q;
memset(vis,0,sizeof vis);
memset(d,0x3f,sizeof d);
memset(pre,-1,sizeof pre);
q.push(s);
d[s] = 0;vis[s] = 1;
while(!q.empty()) {
int top = q.front();
q.pop();
vis[top] = 0;
for(int i = head[top]; i + 1; i = edg[i].nxt) {
int v = edg[i].v,c = edg[i].c,w = edg[i].w;
if(c && d[v] > d[top] + w) {
d[v] = d[top] + w;pre[v] = i;
if(!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
return pre[t] != -1;
}
int maxflow(int s,int t,ll &cost) {
int ans = 0;
cost = 0;
spfa(s,t);
while(spfa(s,t)) {
int mx = inf;
for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v])
mx = min(mx,edg[i].c);
for(int i = pre[t]; i != -1; i = pre[edg[i ^ 1].v]) {
edg[i].c -= mx;
edg[i ^ 1].c += mx;
cost += 1ll * mx * edg[i].w;
}
ans += mx;
}
return ans;
}
int main() {
scanf("%d%d%d",&n,&m,&k);
init();
for(int i = 1,x1,x2,y1,y2,w; i <= k; i++) {
scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&w);
int u = (x1 - 1) * m + y1,v = (x2 - 1) * m + y2;
if((x1 + y1) & 1) swap(u,v);
add(u,v,1,-w);
add(v,u,0,w);
}
int s = 0,t = n * m + 1;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
int u = (i - 1) * m + j;
if((i + j) & 1) {
add(u,t,1,0);
add(t,u,0,0);
}
add(s,u,1,0); //注意 X 部和 Y 部 都要和 s 连边,但只有 Y 部 和 t连边
add(u,s,0,0);
}
}
ll sum = 0;
maxflow(s,t,sum);
printf("%lld\n",-sum);
return 0;
}