洛谷P3159

费用流题,构图非常巧妙。
考虑每个点的交换限制的约束,一看就知道是点容量,但是这里不是一分为二,而是一分为三。
首先我们把问题化简,变成对于原图上所有黑点,找到一个新图中的黑点,进行多次交换后到达。我们看到多次交换实际上是走了一条路径(这里不是最短路)
对于这条路径的起点和终点,仅进行了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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值