费用流题,构图非常巧妙。
考虑每个点的交换限制的约束,一看就知道是点容量,但是这里不是一分为二,而是一分为三。
首先我们把问题化简,变成对于原图上所有黑点,找到一个新图中的黑点,进行多次交换后到达。我们看到多次交换实际上是走了一条路径(这里不是最短路)
。对于这条路径的起点和终点,仅进行了1次交换,而路径上的其他点都交换了2次。所以我们需要构造一种图来把这个交换次数的差异体现出来,于是:
对于每个点一分为三,分为p0(原点),p1(入点),p2(出点),对于每个点,如果它是原图中得黑点,连边 (p1,p0,c/2,0),(p0,p2,(c+1)/2,0),(st,p0,1,0);
如果它是新图中得黑点,连边(p1,p0,(c+1)/2,0),(p0,p2,c/2,0),(p0,ed,1,0);如果它在两个图中都是白点,或者在原图和后来的图中都是黑点,那么连边(p1,p0,c/2,0),(p0,p2,c/2,0)。这样就可以体现出点容量的差异了。
然后对于原图中可以交换的两个点(i,j)连接(pi2,pj1,inf,1),那么这种边每流过1的流量就意味着(i,j)交换了一次,那么费用就是最终的答案了
至于流的大小,只要统计原图中是黑点而后来不是的点的个数即可(cnt)
另一种类似的描述:
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
struct edge {
int to, cap, cost, rev;
};
vector<edge>G[5010];
int dis[5010], prevv[5010], preve[5010], n, m, s, t, flow = 0, cost = 0, cnt = 0;
bool inque[5010];
void add(int from, int to, int cap, int cost)
{
edge e;
e.to = to; e.cap = cap; e.cost = cost; e.rev = G[to].size();
G[from].push_back(e);
e.to = from; e.cap = 0; e.cost = -cost; e.rev = G[from].size() - 1;
G[to].push_back(e);
}
bool Spfa(int s, int t)
{
fill(dis, dis +3000, 1 << 30); memset(inque, 0, sizeof(inque));
queue<int>que;
dis[s] = 0; inque[s] = true; que.push(s);
while (!que.empty()) {
int t = que.front(); que.pop(); inque[t] = false;
for (int i = 0; i < G[t].size(); i++) {
edge e = G[t][i];
if (e.cap&&dis[e.to] > dis[t] + e.cost) {
dis[e.to] = dis[t] + e.cost;
prevv[e.to] = t;
preve[e.to] = i;
if (!inque[e.to]) {
que.push(e.to);
inque[e.to] = true;
}
}
}
}
if (dis[t] == 1 << 30)
return false;
int d = 1 << 30;
for (int v = t; v != s; v = prevv[v])
d = min(d, G[prevv[v]][preve[v]].cap);
flow += d;
cost += d * dis[t];
for (int v = t; v != s; v = prevv[v]) {
edge &e = G[prevv[v]][preve[v]];
e.cap -= d;
G[e.to][e.rev].cap += d;
}
return true;
}
void mincostmaxflow(int s, int t)
{
while (Spfa(s, t)&&flow<cnt);//注意这里的一点小变化
}
int main()
{
int i, j;
cin >> n >> m ;
char t[21];
int mapi[21][21], mapf[21][21], mapl[21][21];//原始图,后来图,容量
for (i = 1; i <= n; i++) {
scanf("%s", t);
for (j = 0; j < m; j++)
mapi[i][j + 1] = t[j] - '0';
}
for (i = 1; i <= n; i++) {
scanf("%s", t);
for (j = 0; j < m; j++)
mapf[i][j + 1] = t[j] - '0';
}
for (i = 1; i <= n; i++) {
scanf("%s", t);
for (j = 0; j < m; j++)
mapl[i][j + 1] = t[j] - '0';
}
for(i=1;i<=n;i++)
for (j = 1; j <= m; j++) {
if (mapi[i][j] == 1 && mapf[i][j] == 1 || mapi[i][j] == 0 && mapf[i][j] == 0) {//如果前后棋子颜色不变
add((i - 1)*m + j + 400, (i - 1)*m + j, mapl[i][j] / 2, 0);
add((i - 1)*m + j, (i - 1)*m + j + 800, mapl[i][j] / 2, 0);
}
else if (mapi[i][j] == 1) {//原来是黑后来是白
add(0, (i - 1)*m + j, 1, 0);
add((i - 1)*m + j + 400, (i - 1)*m + j, mapl[i][j] / 2, 0);
add((i - 1)*m + j, (i - 1)*m + j + 800, (mapl[i][j] + 1) / 2, 0);
cnt++;//注意只要在这里统计变动的棋子数,也就是流量即可
}
else {//后来是黑
add((i - 1)*m + j, 1500, 1, 0);
add((i - 1)*m + j + 400, (i - 1)*m + j, (mapl[i][j] + 1) / 2, 0);
add((i - 1)*m + j, (i - 1)*m + j + 800, mapl[i][j] / 2, 0);
//cnt++;
}
}
int move1[3] = { -1,0,1 }, move2[3] = { -1,0,1 };
for(i=1;i<=n;i++)
for (j = 1; j <= m; j++) {
int a, b;
for(a=0;a<3;a++)
for (b = 0; b < 3; b++) {
if (a == 1 && b == 1)continue;
if (i + move1[a] > 0 && i + move1[a] <= n&&j + move2[b] > 0 && j + move2[b] <= m) {
add((i - 1)*m + j + 800, (i + move1[a] - 1)*m + j + move2[b] + 400, 1 << 30, 1);
}
}
}
mincostmaxflow(0, 1500);
if (flow < cnt)
puts("-1");
else
cout << cost << endl;
return 0;
}