标签: SPFA

[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;
}
[WC2008]游览计划 题解 & 斯坦纳树类题目解法

[WC2008]游览计划 题解 & 斯坦纳树类题目解法

题目地址:洛谷:【P4294】[WC2008]游览计划 – 洛谷、BZOJ:Problem 2595. — [Wc2008]游览计划

题目描述

从未来过绍兴的小D有幸参加了Winter Camp 2008,他被这座历史名城的秀丽风景所吸引,强烈要求游览绍兴及其周边的所有景点。
为了保证安全与便利,主办方依据路况和治安状况,在非景点的一些方块内安排不同数量的志愿者;在景点内聘请导游(导游不是志愿者)。在选择旅游方案时,保证任意两个景点之间,存在一条路径,在这条路径所经过的每一个方块都有志愿者或者该方块为景点。既能满足选手们游览的需要,又能够让志愿者的总数最少。
现在,希望你能够帮助主办方找到一种最好的安排方案。

输入输出格式

输入格式:
第一行有两个整数,N和M,描述方块的数目。
接下来N行,每行有M个非负整数,如果该整数为0,则该方块为一个景点;
否则表示控制该方块至少需要的志愿者数目。相邻的整数用(若干个)空格隔开,
行首行末也可能有多余的空格。

输出格式:
由N+1行组成。第一行为一个整数,表示你所给出的方案中安排的志愿者总数目。
接下来N行,每行M个字符,描述方案中相应方块的情况:
‘_’(下划线)表示该方块没有安排志愿者;
‘o’(小写英文字母o)表示该方块安排了志愿者;
‘x’(小写英文字母x)表示该方块是一个景点;
注:请注意输出格式要求,如果缺少某一行或者某一行的字符数目和要求不一致(任何一行中,多余的空格都不允许出现),都可能导致该测试点不得分。

输入输出样例

输入样例#1:

4 4
0 1 1 0
2 5 5 1
1 5 5 1
0 1 1 0

输出样例#1:

6
xoox
___o
___o
xoox

说明

对于100%的数据,N,M,K≤10,其中K为景点的数目。输入的所有整数均在[0,2^16]的范围内

题解

本题的数据范围又好像是个状压DP,那就想个状压DP呗。要状压的状态应该是这个景点有没有被连接上,而DP状态也就可以设计成dp[i][j][S]表示在(i, j)处且上面的状态为S所需的最少志愿者数。a数组表示每个格子需要的志愿者数目。
现在转移变成了一个问题,首先我们可以考虑一个常规的枚举子集的思路:
dp[i][j][S] = \min_{T \subseteq S} \{ dp[i][j][T] + dp[i][j][S \wedge T] - a[i][j] \}
减一次的原因是这个数被加了两次。
但是不能老是从自己转移啊,四个相邻的格子也可以转移,但是有后效性。我们考虑SPFA转移,对于两个相邻的格子,有下面的转移
dp[i][j][S] = \min\{ dp[i'][j'][S] + a[i][j] \}
其中(i', j')(i, j)相邻。SPFA转移在求最小值的题目中很常见。
上面的这一通思路就是个裸的斯坦纳树。这种问题的模型是:有一个图,要求保留图中最少的边/最小的边权和使得某k个点相互连通。只是这个题需要把上述第二种转移更换为有边相连的点。

代码

// Code by KSkun, 2018/3
#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;
    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 = 11, INF = 0x3f3f3f3f;
const int fix[2][4] = {{-1, 1, 0, 0}, {0, 0, -1, 1}};

struct Point {
    int x, y;
};

struct Data {
    int x, y, s;
} pre[MAXN][MAXN][1 << MAXN];

int n, m, k, mmp[MAXN][MAXN], dp[MAXN][MAXN][1 << MAXN], sx, sy;
std::queue<Point> que;
bool inque[MAXN][MAXN], ans[MAXN][MAXN];

inline void spfa(int s) {
    while(!que.empty()) {
        int x = que.front().x, y = que.front().y; que.pop(); inque[x][y] = false;
        for(int i = 0; i < 4; i++) {
            int nx = x + fix[0][i], ny = y + fix[1][i];
            if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
            if(dp[nx][ny][s] > dp[x][y][s] + mmp[nx][ny]) {
                dp[nx][ny][s] = dp[x][y][s] + mmp[nx][ny];
                pre[nx][ny][s] = Data {x, y, s};
                if(!inque[nx][ny]) {
                    que.push(Point {nx, ny}); inque[nx][ny] = true;
                }
            }
        }
    }
}

inline void dfs(int x, int y, int s) {
    if(!pre[x][y][s].s) return;
    ans[x][y] = true;
    dfs(pre[x][y][s].x, pre[x][y][s].y, pre[x][y][s].s);
    if(pre[x][y][s].x == x && pre[x][y][s].y == y) dfs(x, y, s ^ pre[x][y][s].s);
}

int main() {
    n = readint(); m = readint();
    memset(dp, 0x3f, sizeof(dp));
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(!(mmp[i][j] = readint())) {
                dp[i][j][1 << (k++)] = 0;
                if(!sx) sx = i, sy = j;
            } 
        }
    }
    if(!k) {
        puts("0");
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) putchar('_');
            putchar('\n');
        }
        return 0;
    }
    for(int s = 1; s < (1 << k); s++) {
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                for(int t = s & (s - 1); t; t = s & (t - 1)) {
                    if(dp[i][j][s] > dp[i][j][t] + dp[i][j][s ^ t] - mmp[i][j]) {
                        dp[i][j][s] = dp[i][j][t] + dp[i][j][s ^ t] - mmp[i][j];
                        pre[i][j][s] = Data {i, j, t};
                    }
                }
                if(dp[i][j][s] < INF) que.push(Point {i, j}), inque[i][j] = true;
            }
        }
        spfa(s);
    }
    printf("%d\n", dp[sx][sy][(1 << k) - 1]);
    dfs(sx, sy, (1 << k) - 1);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            if(!mmp[i][j]) putchar('x');
            else if(ans[i][j]) putchar('o');
            else putchar('_');
        }
        putchar('\n');
    }
    return 0;
}