Codeforces Round #304 (Div. 2) E. Soldier and Traveling(最大流 对时间拆点)

本文详细解析了Codeforces Round #304 (Div.2) E题“Soldier and Traveling”的解题思路,介绍了如何通过拆点和建图的方法解决城市间人口迁移问题,并提供了完整的代码实现。

题目链接:Codeforces Round #304 (Div. 2) E. Soldier and Traveling

题意:

有n座城市,城市间有m条双向路。在一天时间内,城市里的人只能选择呆在城市里或到相邻的城市去(只能移动一次)。一开始城市i中有a[i]个人,经过一天的移动后,城市i中有b[i]个人,问是否有一种方案使该移动可行。

解题思路:

1、拆点:
因为题中描述的是城市人口移动前后的状态,所以可以把城市对时间拆点, 将城市X拆为X1和X2,X1代表移动前的城市,X2代表移动后的城市

2、建图:
源点S与X1连容量为a[x]的边,表示移动前城市x有a[x]人;X2与汇点T连容量为b[i]的边,表示移动后城市x有b[x]人。若城市X与城市Y之间有双向路,则X1与Y2连容量为∞的边,Y1与X2连容量为∞的边,表示这两个城市间可以迁移人口,且人口迁移不受限制;同时,X1与X2连容量为∞的边,表示有一部分人始终留在城市X。

3、输出:
若最大流结果 == ∑a[i],说明该移动可以实现,输出移动结果。遍历所有的 边,X1与Y2边 上容量的减少量即为从X到Y的迁 移人数,X1与X2边上 容量的减少量即为留在城市X的人数,用数组保存即可。

注意:
题目给出的∑a[i]可能 != ∑b[i],所以要对∑a[i] == ∑b[i]进行判断。

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<sstream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<utility>
#define JOJO cout<<"JOJO"<<endl;
#define TW system("pause");
using namespace std;

const long long INF = ~0ull>>2;
const int Inf = 0x3f3f3f3f;
const int maxn = 1e3+5;
int Map[105][105];
int head[maxn], cur[maxn];
int lev[maxn];
int t;
int S = 0, T;

struct tagedge{
    int from, to, cap, next;
}edge[maxn*2];

void AddEdge(int u, int v, int w)
{
    edge[t].from = u;
    edge[t].to = v;
    edge[t].cap = w;
    edge[t].next = head[u];
    head[u] = t++;
    return;
}

bool LevelGraph()
{
    queue<int> q;
    fill(lev, lev+maxn, -1);
    lev[S] = 0;
    q.push(S);
    while (!q.empty()){
        int k = q.front();
        q.pop();

        for (int i = head[k]; i != -1; i = edge[i].next){
            int v = edge[i].to;
            if (lev[v] == -1 && edge[i].cap > 0){
                lev[v] = lev[k] + 1;
                q.push(v);
            }
        }
    }

    if (lev[T] == -1)
        return false;
    else
        return true;
}

int FindPath(int k, int flow_in)
{
    if (k == T)
        return flow_in;

    int flow_out = 0;
    for (int i = cur[k]; i != -1; i = edge[i].next){
        cur[k] = i;

        int v = edge[i].to;
        if (lev[v] == lev[k]+1 && edge[i].cap > 0){
            int flow = FindPath(v, min(flow_in, edge[i].cap));
            if (flow == 0) continue;

            flow_in -= flow;
            flow_out += flow;

            edge[i].cap -= flow;
            edge[i^1].cap += flow;

            if (flow_in == 0)   break;
        }
    }
    return flow_out;
}

int Dinic()
{
    int res = 0;
    while (LevelGraph()){
        memcpy(cur, head, sizeof(head));
        res += FindPath(S, Inf);
    }
    return res;
}

void init()
{
    fill(head, head+maxn, -1);
    t = 0;
    return;
}

int main()
{
    init();

    int n, m;
    int sum_a = 0, sum_b = 0;
    scanf("%d%d", &n, &m);
    T = 2*n+5;

    for (int i = 1; i <= n; i++){
        int a;
        scanf("%d", &a);
        sum_a += a;

        /*源点S与X1连容量为a[x]的边,
        表示移动前城市x有a[x]人*/
        AddEdge(S, i, a);
        AddEdge(i, S, 0);

        /*X1与X2连容量为∞的边,
        表示有一部分人始终留在城市X*/
        AddEdge(i, n+i, Inf);
        AddEdge(n+i, i, 0);
    }
    for (int i = 1; i <= n; i++){
        int b;
        scanf("%d", &b);
        sum_b += b;

        /*X2与汇点T连容量为b[i]的边,
        表示移动后城市x有b[x]人*/
        AddEdge(n+i, T, b);
        AddEdge(T, n+i, 0);
    }

    while (m--){
        int u, v;
        scanf("%d%d", &u, &v);

        /*X1与Y2连容量为∞的边,
        Y1与X2连容量为∞的边,
        表示这两个城市间可以迁移人口,
        且人口迁移不受限制*/
        AddEdge(u, n+v, Inf);
        AddEdge(n+v, u, 0);

        AddEdge(v, n+u, Inf);
        AddEdge(n+u, v, 0);
    }

    if (sum_a != sum_b)
        cout << "NO";
    else{
        int res = Dinic();
        if (res != sum_a){
            cout << "NO";
        }
        else{
            for (int k = 1; k <= n; k++){
                for (int i = head[k]; i != -1; i = edge[i].next){
                    int v = edge[i].to;
                    if (v != S){    //有一条反向边连向源点,要排除
                        Map[k][v-n] = Inf - edge[i].cap;
                    }
                }
            }

            cout << "YES" << endl;
            for (int i = 1; i <= n; i++){
                for (int j = 1; j <= n; j++){
                    printf("%d ", Map[i][j]);
                }
                printf("\n");
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值