标签: 最短路

[APIO2013]机器人 题解

[APIO2013]机器人 题解

题目地址:洛谷:【P3638】[APIO2013]机器人 – 洛谷、BZOJ:Problem 3205. — [Apio2013]机器人

题目描述

VRI(Voltron 机器人学会)的工程师建造了 n 个机器人。任意两个兼容的机 器人站在同一个格子时可以合并为一个复合机器人。
我们把机器人用 1 至 n 编号(n ≤ 9)。如果两个机器人的编号是连续的,那 么它们是兼容的,可以合并成一个复合机器人。最初这 n 个机器人各自都只有唯 一的编号。而一个由两个或以上的机器人合并构成的复合机器人拥有两个编号, 分别是构成它的所有机器人中最小和最大的编号。
例如,2 号机器人只可以与 1 号或 3 号机器人合并。若 2 号机器人与 3 号机 器人合并,可构成编号为 2-3 的复合机器人。如果编号为 2-3 的复合机器人与编 号为 4-6 的复合机器人合并,可构成编号为 2-6 的复合机器人。当所有机器人合 并以后则构成 1-n 复合机器人。
工程师把这 n 个机器人放在了一个封闭的房间中,房间四周均是墙。该房间 被划分成 w × h 个方格。有些方格有障碍物,机器人不可经过或停留;其余方格 允许多个机器人停留,同时允许机器人经过。任何时候一个机器人只占用一个方 格。初始时刻,所有机器人均在不同的方格中。
这些原始的机器人不会自发地移动。它们只有被工程师沿 x 轴或 y 轴推动 后,才会沿推动的方向不断向前直线移动,直至碰到障碍物或墙停止移动。停止 移动后,它会扫描当前的格子是否存在可以与它合并的机器人,如果有,则合并 并继续检查,直至不能再合并为止。工程师只能沿水平向左、水平向右、竖直向 上、竖直向下四个方向推动机器人,并且,在机器人尚未停止移动时,不允许推 动其它机器人,因此任何时刻,房间中都只能有一个机器人移动。
为了帮助机器人转向,工程师在一些格子中放置了转向器。具体地说,转向 器分为顺时针转向器(右转器)和逆时针转向器(左转器),顺时针转向器可以 使到达该格子的机器人沿顺时针方向转向 90°;逆时针转向器可以使到达该格 子的机器人沿逆时针方向转向 90°。
现在,我们将告诉你初始时刻房间内的信息。请你计算工程师最少共计需要 推动机器人多少次,才能把所有的 n 个机器人全部合并(如果可能的话)。

输入输出格式

输入格式:
你的程序必须从标准输入读入。
输入的第 1 行包含 3 个整数 n、w 和 h,用 空格隔开。
输入文件中接下来的 h 行描述初始时刻房间内的信息,每行包含 w 个字符。 这 w × h 个字符中每一个表示房间中的一个格子,意义如下:
‘1’至‘9’:表示该方格中有一个机器人,编号为这个数字;
‘x’:表示该方格有障碍物;
‘A’:表示该方格中有一个逆时针转向器;
‘C’:表示该方格中有一个顺时针转向器;
‘.’:表示该方格为空地。

输出格式:
你的程序必须输出到标准输出。输出仅一个整数,表示最少需要推动的次数。 若不能使所有机器人全部合并,输出-1。

输入输出样例

输入样例#1:

4 10 5 
1.........
AA...x4...
..A..x....
2....x....
..C.3.A...

输出样例#1:

5

说明

第一步:向右推动 3 号机器人,当它碰到转向器后会向上继续移动,直至碰 到墙壁停止移动。
第二步:向上推动 4 号机器人,当它碰到墙壁后停止移动,与 3 号机器人合 并,构成 3-4 号机器人
第三步:向上推动 2 号机器人,当它碰到转向器后会向左移动,由于左侧为 墙壁,故停留在原地。
第四步:向右推动 2 号机器人,由于它在一个转向器上,故它会向上移动, 直至碰到墙壁停止移动,与 1 号机器人合并,构成 1-2 号机器人。
第五步:向左推动 3-4 号机器人,当它碰到墙壁后停止移动,与 1-2 号机器 人合并,构成 1-4 号机器人。
我们将使用以下 4 类输入测例测试你的程序。

  1. (10 分)测例满足 n = 2,w ≤ 10 且 h ≤ 10,没有任何转向器。
  2. (20 分)测例满足 n = 2,w ≤ 10 且 h ≤ 10。
  3. (30 分)测例满足 n ≤ 9,w ≤ 300 且 h ≤ 300。
  4. (40 分)测例满足 n ≤ 9,w ≤ 500 且 h ≤ 500。

题解

首先这是一个斯坦纳树的模型,但是比较棘手的是细节部分。
首先在斯坦纳树问题中的边在这里变成了从一个点向某个方向走最终能到达哪里这样的一个问题,就需要用记忆化搜索预处理出这个。而这个DFS细节又比较多,比较难写。
然后我们设计DP状态dp[l][r][i][j]表示在点(i, j)将编号为[l, r]的机器人组合在一起所需的最少推动次数。斯坦纳树问题的第一种转移在这里变成了一个区间DP的套路,方程如下
dp[l][r][i][j] = \min_{k \in [l, r]} \{ dp[l][k][i][j] + dp[k + 1][r][i][j] \}
而第二种转移仍然是从这个点到向某个方向推的下一个点
dp[l][r][i][j] = \min \{ dp[l][r][i'][j'] + 1 \}
第二种转移依旧要依赖于SPFA来做,可是SPFA的复杂度不靠谱,我们考虑优化成下面的形式。
我们把SPFA的出发点集合按dp值先排个序。由于dp值不会很大,可以采用计数排序,实现O(n)的排序。把排序后的点扔进队列Q1,然后把扩展出来的点扔进队列Q2。每次要松弛的时候从两个队列的队首拿dp值最小的来松弛。这种优化方法只有当图的边权全部为1的时候才能用。
答案就是\min \{ dp[1][n][i][j] \}
吐槽:洛谷上这个题卡常的厉害,我优化成底下这个代码的样子还没过,最后开了O2苟过去的。

代码

题解代码参考并借鉴了[APIO2013] – FallDream – 博客园中的写法,感谢原作者。

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

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 int readint() {
    register int 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;
}

inline bool ismap(register char c) {
    return c == 'A' || c == 'C' || c == '.' || c == 'x' || (c > '0' && c <= '9');
}

inline char readmap() {
    register char c;
    while(!ismap(c = fgc()));
    return c;
}

const int MAXK = 10, MAXN = 505, INF = 0x3f3f3f3f;
const int fix[2][4] = {{-1, 0, 1, 0}, {0, 1, 0, -1}};

int k, n, m, clk, top; 
int dp[MAXK][MAXK][MAXN][MAXN], vis[MAXN][MAXN][4], v[200005], rk[200005];
char mmp[MAXN][MAXN];
bool inque[MAXN][MAXN];

struct Point {
    int x, y;
} pts[MAXK], nxt[MAXN][MAXN][4], q[250005];

struct Queue {
    Point q[250005];
    static const int len = 250000;
    int l, r;
    Queue() {
        l = r = 0;
    }
    inline void clear() {
        l = r = 0;
    }
    inline void push(Point p) {
        q[r++] = p;
        if(r > len) r -= len;
    }
    inline Point front() {
        return q[l];
    }
    inline void pop() {
        l++;
        if(l > len) l -= len;
    }
    inline bool empty() {
        return l == r;
    }
}; 

Queue q1, q2;

inline Point dfs(int x, int y, int dir) {
    if(vis[x][y][dir] == clk) {
        nxt[x][y][dir].x = nxt[x][y][dir].y = -1; 
        return nxt[x][y][dir];
    }
    vis[x][y][dir] = clk;
    if(nxt[x][y][dir].x && nxt[x][y][dir].y) return nxt[x][y][dir];
    register int ndir = dir;
    if(mmp[x][y] == 'A') ndir = dir + 3;
    else if(mmp[x][y] == 'C') ndir = dir + 1;
    if(ndir >= 4) ndir -= 4;
    register int nx = x + fix[0][ndir], ny = y + fix[1][ndir];
    if(nx < 1 || nx > n || ny < 1 || ny > m || mmp[nx][ny] == 'x') {
        nxt[x][y][dir].x = x;
        nxt[x][y][dir].y = y;
        return nxt[x][y][dir];
    }
    return nxt[x][y][dir] = dfs(nx, ny, ndir);
}

inline void spfa(register int l, register int r) {
    // 计数排序
    memset(v, 0, sizeof(v));
    register int mx = 0;
    for(register int i = 1; i <= top; i++) {
        if(mx < dp[l][r][q[i].x][q[i].y]) mx = dp[l][r][q[i].x][q[i].y];
        v[dp[l][r][q[i].x][q[i].y]]++;
    }
    for(register int i = 1; i <= mx; i++) v[i] += v[i - 1];
    for(register int i = 1; i <= top; i++) rk[v[dp[l][r][q[i].x][q[i].y]]--] = i;
    for(register int i = 1; i <= top; i++) q1.push(q[rk[i]]);
    top = 0;
    // SPFA
    while(!q1.empty() || !q2.empty()) {
        Point t;
        if(q1.empty() || (!q2.empty() && dp[l][r][q1.front().x][q1.front().y] > dp[l][r][q2.front().x][q2.front().y])) {
            t = q2.front(); q2.pop();
        } else {
            t = q1.front(); q1.pop();
        }
        inque[t.x][t.y] = false;
        for(register int i = 0; i < 4; i++) {
            Point nx = nxt[t.x][t.y][i];
            if(nx.x == -1 || nx.y == -1) continue;
            if(dp[l][r][nx.x][nx.y] > dp[l][r][t.x][t.y] + 1) {
                dp[l][r][nx.x][nx.y] = dp[l][r][t.x][t.y] + 1;
                if(!inque[nx.x][nx.y]) {
                    inque[nx.x][nx.y] = true;
                    q2.push(nx);
                }
            }
        }
    }
}

int main() {
    k = readint(); m = readint(); n = readint();
    for(register int i = 1; i <= n; i++) {
        for(register int j = 1; j <= m; j++) {
            mmp[i][j] = readmap();
            if(mmp[i][j] > '0' && mmp[i][j] <= '9') {
                int t = mmp[i][j] - '0';
                pts[t].x = i;
                pts[t].y = j;
            }
        }
    }
    for(register int i = 1; i <= n; i++) {
        for(register int j = 1; j <= m; j++) {
            if(mmp[i][j] != 'x') {
                for(register int k = 0; k < 4; k++) {
                    clk++;
                    dfs(i, j, k);
                }
            }
        }
    }
    memset(dp, 0x3f, sizeof(dp));
    for(register int i = 1; i <= k; i++) {
        inque[pts[i].x][pts[i].y] = true;
        q[++top] = pts[i];
        dp[i][i][pts[i].x][pts[i].y] = 0;
        spfa(i, i);
    }
    for(register int len = 2; len <= k; len++) {
        for(register int l = 1; l + len - 1 <= k; l++) {
            register int r = l + len - 1;
            for(register int i = 1; i <= n; i++) {
                for(register int j = 1; j <= m; j++) {
                    for(register int k = l; k < r; k++) {
                        if(dp[l][r][i][j] > dp[l][k][i][j] + dp[k + 1][r][i][j]) {
                            dp[l][r][i][j] = dp[l][k][i][j] + dp[k + 1][r][i][j];
                        }
                    }
                    if(dp[l][r][i][j] < INF) {
                        q[++top].x = i;
                        q[top].y = j;
                        inque[i][j] = true;
                    }
                }
            }
            spfa(l, r);
        }
    }
    register int ans = INF;
    for(register int i = 1; i <= n; i++) {
        for(register int j = 1; j <= m; j++) {
            if(ans > dp[1][k][i][j]) ans = dp[1][k][i][j];
        }
    }
    if(ans == INF) {
        puts("-1");
    } else {
        printf("%d", ans);
    }
    return 0;
}
[HDU6166]Senior Pan 题解

[HDU6166]Senior Pan 题解

题目地址:HDUOJ:Problem – 6166

题目描述

Senior Pan fails in his discrete math exam again. So he asks Master ZKC to give him graph theory problems everyday.
The task is simple : ZKC will give Pan a directed graph every time, and selects some nodes from that graph, you can calculate the minimum distance of every pair of nodes chosen in these nodes and now ZKC only cares about the minimum among them. That is still too hard for poor Pan, so he asks you for help.
给定一个n个点m条边的有向带权图,其中有k个特殊点,求这k个特殊点之间两两最短路的最小值。

输入输出格式

输入格式:
The first line contains one integer T, represents the number of Test Cases.1≤T≤5.Then T Test Cases, for each Test Cases, the first line contains two integers n,m representing the number of nodes and the number of edges.1≤n,m≤100000
Then m lines follow. Each line contains three integers xi,yi representing an edge, and vi representing its length.1≤xi,yi≤n,1≤vi≤100000
Then one line contains one integer K, the number of nodes that Master Dong selects out.1≤K≤n
The following line contains K unique integers ai, the nodes that Master Dong selects out.1≤ai≤n,ai!=aj

输出格式:
For every Test Case, output one integer: the answer

输入输出样例

输入样例#1:

1
5 6
1 2 1
2 3 3
3 1 3
2 5 1
2 4 2
4 3 1
3
1 3 5

输出样例#1:

Case #1: 2

题解

首先多源最短路Dijkstra是可以做的,但是这里并不是起点集合到终点集合,而是直接给你扔一个点集来求。如果暴力地算,k一大就GG了。我们需要一种划分手段,减少求最短路的次数。
这里要提到一种技巧,叫二进制划分。指的是对于二进制的每一位是0还是1进行集合划分。例如本题,点的编号最大为100000,不会超过20位二进制,那么我们对于20位二进制每一位都划分一次,这样最短路的计算次数就减少为20次了。这样做的复杂度只会在Dijkstra的复杂度上乘一个\log n,是可以接受的。
为什么把所有划分跑完最短路后就能得到答案呢?因为点的编号都不相同,那么二进制至少有一位不相同,可以保证任意两个点在某一次划分中被分开了。这也就是说,我们相当于把每两点之间的最短路都计算了一遍。
需要注意的是,由于是有向图,0集合到1集合跑一遍,反过来还要跑一遍。

代码

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

#include <vector>
#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;
    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;
const LL INF = 0x3f3f3f3f3f3f3f3fll;

int T, n, m, ut, vt, wt, k, a[MAXN];
LL dis[MAXN];
bool vis[MAXN], goal[MAXN];

struct Edge {
    int to;
    LL w;
};

struct Node {
    int u;
    LL d;
    bool operator<(const Node &rhs) const {
        return d > rhs.d;
    }
};

std::vector<Edge> gra[MAXN];
std::vector<int> v0, v1;
std::priority_queue<Node> pq;

inline LL dijkstra(std::vector<int> st) {
    while(!pq.empty()) pq.pop();
    memset(vis, 0, sizeof(vis));
    memset(dis, 0x3f, sizeof(dis));
    for(int i = 0; i < st.size(); i++) {
        int u = st[i];
        dis[u] = 0;
        pq.push(Node {u, 0});
    }
    while(!pq.empty()) {
        int u = pq.top().u; LL d = pq.top().d; pq.pop();
        if(goal[u]) return d;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = 0; i < gra[u].size(); i++) {
            int v = gra[u][i].to, w = gra[u][i].w;
            if(!vis[v] && dis[v] > dis[u] + w) {
                dis[v] = dis[u] + w;
                pq.push(Node {v, dis[v]});
            }
        }
    }
    return INF;
}

int main() {
    T = readint();
    for(int kase = 1; kase <= T; kase++) {
        n = readint(); m = readint();
        for(int i = 1; i <= n; i++) {
            gra[i].clear();
        }
        for(int i = 1; i <= m; i++) {
            ut = readint(); vt = readint(); wt = readint();
            gra[ut].push_back(Edge {vt, wt});
        }
        k = readint();
        for(int i = 1; i <= k; i++) {
            a[i] = readint();
        }
        LL ans = INF;
        for(int i = 0; i < 20; i++) {
            v0.clear(); v1.clear();
            // v0 to v1 shortest path
            memset(goal, 0, sizeof(goal));
            for(int j = 1; j <= k; j++) {
                int u = a[j];
                if(u & (1 << i)) {
                    v1.push_back(u);
                } else {
                    v0.push_back(u);
                    goal[u] = true;
                }
            }
            ans = std::min(ans, dijkstra(v1));
            // v1 to v0 shortest path
            memset(goal, 0, sizeof(goal));
            for(int j = 0; j < v1.size(); j++) {
                goal[v1[j]] = true;
            }
            ans = std::min(ans, dijkstra(v0));
        }
        printf("Case #%d: %lld\n", kase, ans);
    }
    return 0;
}
[CF543B]Destroying Roads 题解

[CF543B]Destroying Roads 题解

题目地址:Codeforces:Problem – 666B – Codeforces、洛谷:【CF666B】World Tour – 洛谷

题目描述

In some country there are exactly n cities and m bidirectional roads connecting the cities. Cities are numbered with integers from 1 to n. If cities a and b are connected by a road, then in an hour you can go along this road either from city a to city b, or from city b to city a. The road network is such that from any city you can get to any other one by moving along the roads.
You want to destroy the largest possible number of roads in the country so that the remaining roads would allow you to get from city s1 to city t1 in at most l1 hours and get from city s2 to city t2 in at most l2 hours.
Determine what maximum number of roads you need to destroy in order to meet the condition of your plan. If it is impossible to reach the desired result, print -1.
求最多能删去的边数,且删去后满足dis[s1][t1]<=l1,dis[s2][t2]<=l2。

输入输出格式

输入格式:
The first line contains two integers n, m (1 ≤ n ≤ 3000, n - 1 \leq m \leq \min\{3000, \frac{n(n-1)}{2}\}) — the number of cities and roads in the country, respectively.
Next m lines contain the descriptions of the roads as pairs of integers ai, bi (1 ≤ ai, bi ≤ n, ai ≠ bi). It is guaranteed that the roads that are given in the description can transport you from any city to any other one. It is guaranteed that each pair of cities has at most one road between them.
The last two lines contains three integers each, s1, t1, l1 and s2, t2, l2, respectively (1 ≤ si, ti ≤ n, 0 ≤ li ≤ n).

输出格式:
Print a single number — the answer to the problem. If the it is impossible to meet the conditions, print -1.

输入输出样例

输入样例#1:

5 4
1 2
2 3
3 4
4 5
1 3 2
3 5 2

输出样例#1:

0

输入样例#2:

5 4
1 2
2 3
3 4
4 5
1 3 2
2 4 2

输出样例#2:

1

输入样例#3:

5 4
1 2
2 3
3 4
4 5
1 3 2
3 5 1

输出样例#3:

-1

题解

首先BFS预处理出所有点之间的最短路。如果dis[s1][t1]和dis[s2][t2]没删边就已经不满足了那删边是没有用的,这种情况就是无解的情况。
接着考虑在原本的最短路上有什么方法可以使最短路覆盖的边数减少。我们可以让两条路径中间有一段重合,兴许能减少边数。为什么是一段?因为如果有多段重合,把多段重合之间没重合的改成重合的显然更优。这样我们枚举重合的一段的端点,并且计算总长,更新答案。需要注意的是,s1/s2→u→v→t1/t2与s1→u→v→t1/s2→v→u→t2这两种情况都要考虑,因为是无向图可以双向通行嘛。
复杂度O(n^2)

代码

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

#include <vector>
#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 = 3005;

int n, m, ut, vt, s1, t1, l1, s2, t2, l2, dis[MAXN][MAXN], ans;
bool vis[MAXN];
std::vector<int> gra[MAXN];
std::queue<int> que;

inline void bfs(int st) {
    int *dist = dis[st];
    memset(vis, 0, sizeof(vis));
    que.push(st);
    vis[st] = true;
    dist[st] = 0;
    while(!que.empty()) {
        int u = que.front();
        que.pop();
        for(int v : gra[u]) {
            if(vis[v]) continue;
            vis[v] = true; 
            dist[v] = dist[u] + 1;
            que.push(v);
        }
    }
}

inline void updateans(int u, int v) {
    int c1 = dis[s1][u] + dis[u][v] + dis[v][t1], c2 = dis[s2][u] + dis[u][v] + dis[v][t2], c = c1 + c2 - dis[u][v];
    if(c1 <= l1 && c2 <= l2) ans = std::min(ans, c);
    c2 = dis[s2][v] + dis[u][v] + dis[u][t2], c = c1 + c2 - dis[u][v];
    if(c1 <= l1 && c2 <= l2) ans = std::min(ans, c);
}

int main() {
    memset(dis, 0x3f, sizeof(dis));
    n = readint(), m = readint();
    for(int i = 1; i <= m; i++) {
        ut = readint(), vt = readint();
        gra[ut].push_back(vt);
        gra[vt].push_back(ut);
    }
    s1 = readint(), t1 = readint(), l1 = readint();
    s2 = readint(), t2 = readint(), l2 = readint();
    for(int i = 1; i <= n; i++) {
        bfs(i);
    }
    if(dis[s1][t1] > l1 || dis[s2][t2] > l2) {
        printf("-1");
        return 0;
    }
    ans = dis[s1][t1] + dis[s2][t2];
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            updateans(i, j);
        }
    }
    printf("%d", m - ans);
    return 0;
}