标签: 最小割

[TopCoder12727]FoxAndCity 题解

[TopCoder12727]FoxAndCity 题解

题目地址:TopCoder Arena:Practice – TopCoder Arena、vjudge:CurvyonRails – TopCoder 12432 – Virtual Judge

题目描述

There is a country with n cities, numbered 0 through n-1. City 0 is the capital. The road network in the country forms an undirected connected graph. In other words: Some pairs of cities are connected by bidirectional roads.
For every city there is at least one sequence of consecutive roads that leads from the city to the capital. (Whenever two roads need to cross outside of a city, the crossing is done using a bridge, so there are no intersections outside of the cities.)
You are given a linked that describes the road network. For each i and j, linked[i][j] is ‘Y’ if the cities i and j are already connected by a direct road, and it is ‘N’ otherwise.
The distance between two cities is the smallest number of roads one needs to travel to get from one city to the other. The people living outside of the capital are usually unhappy about their distance from the capital. You are also given a want with n elements. For each i, want[i] is the desired distance between city i and the capital (city 0).
Fox Ciel is in charge of building new roads. Each new road must again be bidirectional and connect two cities. Once the new roads are built, the citizens will evaluate how unhappy they are with the resulting road network:
For each i: Let real[i] be the new distance between city i and the capital. Then the people in city i increase the unhappiness of the country by (want[i] – real[i])^2.
Return the minimal total unhappiness Ciel can achieve by building some (possibly zero) new roads.
给定一个n个点的图,你现在可以往图上加边,每个点有个值want[i],加边后每个点到0的最短路为real[i],要求最小化\sum_{i=0}^{n-1} (want[i] - real[i])^2

定义名

类名:FoxAndCity
函数名:minimalCost
参数:vector <string>, vector <int>
返回值:int
函数声明:int minimalCost(vector <string> linked, vector <int> want)

样例

样例#1:

{"NYN", "YNY", "NYN"}
{0, 1, 1}

样例#1结果:

0

其他样例参见原网站。

说明

  • n will be between 2 and 40, inclusive.
  • linked will contain exactly n elements.
  • Each element of linked will contain exactly n characters.
  • Each character in linked will be ‘Y’ or ‘N’.
  • For each i and j, linked[i][j] = linked[j][i].
  • For each i, linked[i][i] = ‘N’.
  • The graph described by linked will be connected.
  • want will contain exactly n elements.
  • Each element in want will be between 0 and n-1, inclusive.
  • want[0] will be 0.

题解

参考资料:[最小割] SRM 590 div1 FoxAndCity – CSDN博客
我们考虑有以下性质:

  1. real[0] = 0real[i] \neq 0 \ (i \neq 0)
  2. 如果有边 (u, v) \in E,则 |real[u] - real[v]| \leq 1

因此我们可以建立最小割模型,类似[HNOI2013]切糕 题解 | KSkun’s Blog,对每个点的n-1种real[i]取值(0, 1, …, n-1)建点连成链,设置每个点对应的边的容量为选择该real[i]对答案的贡献,其中源→(i, real[i]=0)的点容量无限,因为要满足性质1。对于原图中有边相连的点(u, v),我们建边(u, real[u]=k)→(v, real[v]=k-1)表示性质2的限制。
注意0点的real只能取0,因此我们的源→(0, real[0]=0)边容量为0,可以选择不建。但是要建(0, real[0]=0)→T的边,容量无限,用于限制与0有边相连的点的real值。

代码

由于TopCoder要求实现一个class,下面的代码并不是标准输入/输出格式(传统OI题目)的实现方式,但是把调试注释给去掉就可以用标准输入/输出了。

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <vector>
#include <string>
#include <queue>

const int MAXN = 1000005, INF = 1e9;

struct Edge {
    int to, cap, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void addedge(int u, int v, int cap) {
    gra[tot] = Edge {v, cap, head[u]}; head[u] = tot++;
    gra[tot] = Edge {u, 0, head[v]}; head[v] = tot++;
}

inline void reset() {
    memset(head, -1, sizeof(head)); tot = 0;
}

int level[MAXN];

inline bool bfs(int s, int t) {
    memset(level, -1, sizeof(level));
    std::queue<int> que;
    level[s] = 0; que.push(s);
    while(!que.empty()) {
        int u = que.front(); que.pop();
        for(int i = head[u]; ~i; i = gra[i].nxt) {
            int v = gra[i].to;
            if(gra[i].cap > 0 && level[v] == -1) {
                level[v] = level[u] + 1;
                if(v == t) return true;
                que.push(v);
            }
        }
    }
    return level[t] != -1;
}

int cur[MAXN];

inline int dfs(int u, int t, int left) {
    if(u == t || !left) return left;
    int flow = 0;
    for(int &i = cur[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(gra[i].cap > 0 && level[v] == level[u] + 1) {
            int d = dfs(v, t, std::min(left, gra[i].cap));
            if(d > 0) {
                flow += d; left -= d;
                gra[i].cap -= d; gra[i ^ 1].cap += d;
                if(!left) return flow;
            }
        }
    }
    return flow;
}

inline int dinic(int s, int t) {
    int flow = 0;
    while(bfs(s, t)) {
        memcpy(cur, head, sizeof(head));
        int f;
        while(f = dfs(s, t, INF)) {
            flow += f;
        }
    }
    return flow;
}

int n, S, T;

class FoxAndCity {
public:
    inline int minimalCost(std::vector<std::string> linked, std::vector<int> want) {
        n = linked.size();
        S = n * n + 1; T = S + 1;
        reset();
        addedge(1, T, INF);
        for(int i = 2; i <= n; i++) {
            addedge(S, (i - 1) * n + 1, INF);
            for(int j = 1; j < n; j++) {
                addedge((i - 1) * n + j, (i - 1) * n + j + 1, 
                    (j - want[i - 1]) * (j - want[i - 1]));
            }
            addedge((i - 1) * n + n, T, INF);
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(linked[i - 1][j - 1] == 'Y') {
                    for(int k = 1; k < n; k++) {
                        addedge((i - 1) * n + k + 1, (j - 1) * n + k, INF);
                    }
                }
            }
        }
        return dinic(S, T);
    }
};

/*int main() {
    FoxAndCity solver;
    printf("%d", solver.minimalCost({"NYNNNY", "YNYNNN", "NYNYNN", "NNYNYN", 
        "NNNYNY", "YNNNYN"}, {0, 2, 2, 2, 2, 2}));
    return 0;
}*/
[NOI2010]海拔 题解

[NOI2010]海拔 题解

题目地址:洛谷:【P2046】[NOI2010]海拔 – 洛谷、BZOJ:Problem 2007. — [Noi2010]海拔

题目描述

YT市是一个规划良好的城市,城市被东西向和南北向的主干道划分为n×n个区域。简单起见,可以将YT市看作 一个正方形,每一个区域也可看作一个正方形。从而,YT城市中包括(n+1)×(n+1)个交叉路口和2n×(n+1)条双向道路(简称道路),每条双向 道路连接主干道上两个相邻的交叉路口。下图为一张YT市的地图(n = 2),城市被划分为2×2个区域,包括3×3个交叉路口和12条双向道路。
altitude noi10 - [NOI2010]海拔 题解
小Z作为该市的市长,他根据统计信息得到了每天上班高峰期间YT市每条道路两个方向的人流量,即在高峰期间沿 着该方向通过这条道路的人数。每一个交叉路口都有不同的海拔高度值,YT市市民认为爬坡是一件非常累的事情,每向上爬h的高度,就需要消耗h的体力。如果 是下坡的话,则不需要耗费体力。因此如果一段道路的终点海拔减去起点海拔的值为h(注意h可能是负数),那么一个人经过这段路所消耗的体力是max{0, h}(这里max{a, b}表示取a, b两个值中的较大值)。
小Z还测量得到这个城市西北角的交叉路口海拔为0,东南角的交叉路口海拔为1(如上图所示),但其它交叉路口的海拔高度都无法得知。小Z想知道在最理想的情况下(即你可以任意假设其他路口的海拔高度),每天上班高峰期间所有人爬坡消耗的总体力和的最小值。

输入输出格式

输入格式:
第一行包含一个整数n,含义如上文所示。
接下来4n(n + 1)行,每行包含一个非负整数分别表示每一条道路每一个方向的人流量信息。输入顺序:n(n + 1)个数表示所有从西到东方向的人流量,然后n(n + 1)个数表示所有从北到南方向的人流量,n(n + 1)个数表示所有从东到西方向的人流量,最后是n(n + 1)个数表示所有从南到北方向的人流量。对于每一个方向,输入顺序按照起点由北向南,若南北方向相同时由西到东的顺序给出(参见样例输入)。

输出格式:
仅包含一个数,表示在最理想情况下每天上班高峰期间所有人爬坡所消耗的总体力和(即总体力和的最小值),结果四舍五入到整数。

输入输出样例

输入样例#1:

1
1
2
3
4
5
6
7
8

输出样例#1:

3

说明

对于20%的数据:n ≤ 3;
对于50%的数据:n ≤ 15;
对于80%的数据:n ≤ 40;
对于100%的数据:1 ≤ n ≤ 500,0 ≤ 流量 ≤ 1,000,000且所有流量均为整数。

题解

我们发现这些方格在最优解上的高度肯定非0即1,且有一条明显的分界线。因此我们只需要找出一些跨越分界线的边,计算它们的贡献即可。这实际上可以看成一个最小割。
既然我们把原问题转化成了最小割问题,就可以继续转化为对偶图上的最短路问题。由于是方格图,对偶图很好建立。
需要注意的是双向边问题,我们只需要在对偶图上也建立方向相反权值不同的有向边即可。例如我们将向下向右的边建为S→T方向,则向上向左的边要建为T→S方向,随后跑一遍最短路即可。通过验证,S、T的选择与边的方向选择的两种方案是等价的,因此可以任意选择S和T的位置。下面的代码中,上边界和右边界与T相连,下边界和左边界与S相连。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <queue>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 1000005;

struct Edge {
    int to, w, nxt;
} gra[MAXN << 1];
int head[MAXN], tot;

inline void addedge(int u, int v, int w) {
    gra[tot] = Edge {v, w, head[u]}; head[u] = tot++;
}

int dis[MAXN];
bool inque[MAXN];

struct Queue {
    int l, r, q[MAXN];
    inline void clear() {
        l = r = 0;
    }
    Queue() {
        clear();
    }
    inline int front() {
        return q[l];
    }
    inline void push(int x) {
        q[r++] = x;
        if(r >= MAXN) r -= MAXN;
    }
    inline void pop() {
        l++;
        if(l >= MAXN) l -= MAXN;
    }
    inline bool empty() {
        return l == r;
    }
} que;

inline void spfa(int s) {
    que.clear();
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0; inque[s] = true;
    que.push(s);
    while(!que.empty()) {
        register int u = que.front(); que.pop(); inque[u] = false;
        for(int i = head[u]; ~i; i = gra[i].nxt) {
            register int v = gra[i].to;
            if(dis[v] > dis[u] + gra[i].w) {
                dis[v] = dis[u] + gra[i].w;
                if(!inque[v]) {
                    inque[v] = true;
                    que.push(v);
                }
            }
        }
    }
}

int n, t, S, T;

int main() {
    memset(head, -1, sizeof(head));
    n = readint();
    S = 0; T = n * n + 1;
    for(register int i = 1; i <= n + 1; i++) {
        for(register int j = 1; j <= n; j++) {
            t = readint();
            if(i == 1) {
                addedge((i - 1) * n + j, T, t);
            } else if(i == n + 1) {
                addedge(S, (i - 2) * n + j, t);
            } else {
                addedge((i - 1) * n + j, (i - 2) * n + j, t);
            }
        }
    }
    for(register int i = 1; i <= n; i++) {
        for(register int j = 1; j <= n + 1; j++) {
            t = readint();
            if(j == 1) {
                addedge(S, (i - 1) * n + j, t);
            } else if(j == n + 1) {
                addedge((i - 1) * n + j - 1, T, t);
            } else {
                addedge((i - 1) * n + j - 1, (i - 1) * n + j, t);
            }
        }
    }
    for(register int i = 1; i <= n + 1; i++) {
        for(register int j = 1; j <= n; j++) {
            t = readint();
            if(i == 1) {
                addedge(T, (i - 1) * n + j, t);
            } else if(i == n + 1) {
                addedge((i - 2) * n + j, S, t);
            } else {
                addedge((i - 2) * n + j, (i - 1) * n + j, t);
            }
        }
    }
    for(register int i = 1; i <= n; i++) {
        for(register int j = 1; j <= n + 1; j++) {
            t = readint();
            if(j == 1) {
                addedge((i - 1) * n + j, S, t);
            } else if(j == n + 1) {
                addedge(T, (i - 1) * n + j - 1, t);
            } else {
                addedge((i - 1) * n + j, (i - 1) * n + j - 1, t);
            }
        }
    }
    spfa(S);
    printf("%d", dis[T]);
    return 0;
}
[CEOI2008]Order 题解

[CEOI2008]Order 题解

题目地址:BZOJ:Problem 1391. — [Ceoi2008]order

题目描述

有N个工作,M种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序,每道工序需要某种机器来完成,你可以通过购买或租用机器来完成。 现在给出这些参数,求最大利润

输入输出格式

输入格式:
第一行给出 N,M(1<=N<=1200,1<=M<=1200) 下面将有N块数据,每块数据第一行给出完成这个任务能赚到的钱(其在[1,5000])及有多少道工序 接下来若干行每行两个数,分别描述完成工序所需要的机器编号及租用它的费用(其在[1,20000]) 最后M行,每行给出购买机器的费用(其在[1,20000])

输出格式:
最大利润

输入输出样例

输入样例#1:

2 3
100 2
1 30
2 20
100 2
1 40
3 80
50
80
110

输出样例#1:

50

题解

如果没有借机器这个项目,本题就是个裸的最大权闭合子图问题。
下面考虑借机器怎么来操作。我们之前的模型是通过割机器-汇点的边来表示购买机器,这条边只能割一次,能不能找到一种边使得每进行涉及该机器的任务的时候都会被割一次。此时我们想到了原来任务-机器之间的无限流的边,只要将这条边的容量设置为借机器的费用,割这条边的时候就代表了借了一次机器。

代码

// Code by KSkun, 2018/4
#include <cstdio>
#include <cstring>

#include <algorithm>
#include <queue>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 100005, INF = 1e9;

struct Edge {
    int to, cap, nxt;
} gra[MAXN << 5];
int head[MAXN], tot;

inline void addedge(int u, int v, int cap) {
    gra[tot] = Edge {v, cap, head[u]}; head[u] = tot++;
    gra[tot] = Edge {u, 0, head[v]}; head[v] = tot++;
}

int level[MAXN];

inline bool bfs(int s, int t) {
    memset(level, -1, sizeof(level));
    std::queue<int> que;
    level[s] = 0; que.push(s);
    while(!que.empty()) {
        int u = que.front(); que.pop();
        for(int i = head[u]; ~i; i = gra[i].nxt) {
            int v = gra[i].to;
            if(level[v] == -1 && gra[i].cap > 0) {
                level[v] = level[u] + 1;
                if(v == t) return true;
                que.push(v);
            }
        }
    }
    return level[t] != -1;
}

int cur[MAXN];

inline int dfs(int u, int t, int left) {
    if(u == t || left <= 0) return left;
    int flow = 0;
    for(int &i = cur[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(gra[i].cap > 0 && level[v] == level[u] + 1) {
            int d = dfs(v, t, std::min(left, gra[i].cap));
            if(d > 0) {
                left -= d; flow += d;
                gra[i].cap -= d; gra[i ^ 1].cap += d;
                if(left <= 0) return flow;
            }
        }
    }
    return flow;
}

inline int dinic(int s, int t) {
    int flow = 0;
    while(bfs(s, t)) {
        memcpy(cur, head, sizeof(head));
        int f;
        while(f = dfs(s, t, INF)) {
            flow += f;
        }
    }
    return flow;
}

int n, m, prof, tc, borr, borc, buyc, S, T, ans;

// 1 ~ n task
// n+1 ~ n+m machine

int main() {
    memset(head, -1, sizeof(head));
    n = readint(); m = readint();
    S = n + m + 1; T = S + 1;
    for(int i = 1; i <= n; i++) {
        prof = readint(); tc = readint();
        ans += prof;
        addedge(S, i, prof);
        while(tc--) {
            borr = readint(); borc = readint();
            addedge(i, borr + n, borc);
        }
    }
    for(int i = 1; i <= m; i++) {
        buyc = readint();
        addedge(i + n, T, buyc);
    }
    ans -= dinic(S, T);
    printf("%d", ans);
    return 0;
}