Codeforces 821D Okabe and City (拆点+思维建图+spfa)

来源:互联网 发布:淘宝邀请的活动好不好 编辑:程序博客网 时间:2024/06/07 03:17

D. Okabe and City
time limit per test
4 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Okabe likes to be able to walk through his city on a path lit by street lamps. That way, he doesn't get beaten up by schoolchildren.

Okabe's city is represented by a 2D grid of cells. Rows are numbered from 1 to n from top to bottom, and columns are numbered 1 to mfrom left to right. Exactly k cells in the city are lit by a street lamp. It's guaranteed that the top-left cell is lit.

Okabe starts his walk from the top-left cell, and wants to reach the bottom-right cell. Of course, Okabe will only walk on lit cells, and he can only move to adjacent cells in the up, down, left, and right directions. However, Okabe can also temporarily light all the cells in any single row or column at a time if he pays 1 coin, allowing him to walk through some cells not lit initially.

Note that Okabe can only light a single row or column at a time, and has to pay a coin every time he lights a new row or column. To change the row or column that is temporarily lit, he must stand at a cell that is lit initially. Also, once he removes his temporary light from a row or column, all cells in that row/column not initially lit are now not lit.

Help Okabe find the minimum number of coins he needs to pay to complete his walk!

Input

The first line of input contains three space-separated integers nm, and k (2 ≤ n, m, k ≤ 104).

Each of the next k lines contains two space-separated integers ri and ci (1 ≤ ri ≤ n1 ≤ ci ≤ m) — the row and the column of the i-th lit cell.

It is guaranteed that all k lit cells are distinct. It is guaranteed that the top-left cell is lit.

Output

Print the minimum number of coins Okabe needs to pay to complete his walk, or -1 if it's not possible.

Examples
Input
4 4 51 12 12 33 34 3
Output
2
Input
5 5 41 12 13 13 2
Output
-1
Input
2 2 41 11 22 12 2
Output
0
Input
5 5 41 12 23 34 4
Output
3
Note

In the first sample test, Okabe can take the path , paying only when moving to (2, 3) and (4, 4).

In the fourth sample, Okabe can take the path  , paying when moving to (1, 2)(3, 4), and (5, 4).


题目大意:


现在给你N*M的一个矩阵,现在上边一共有K个永恒亮着的点,主人公从左上角出发,走到的点必须有亮光才行。

但是现在不保证有亮光的点能够使得主人公到达右下角,所以他可以花费1单位金币去使得一行或者一列暂时性的亮着,如果他想再次使用魔法,那么之前暂时亮着的部分就必须灭掉了。

问他最少花费多少金币,能够从左上角走到有下角。

如果不能走到,输出-1.




思路:这题跟计蒜客复赛 百度地图导航很像,但这题比较难想到竟然最短路做- -,拆点方式,已经建图方式很巧妙,所以我详细说说这题~

如果是我,我可能会dfs来一发。。。不过肯定t,这题要明白,如果两个原来亮的点相邻,肯定就不用花钱就到了, 如果不相邻,只有这几种可能性他可以到达另一个点:

1,他们在同一列或者同一行, 2,他们的列或者行相邻,这时候就点亮相邻这一行/列,就可以到达那行的任意一点了3,他们列或者行坐标差值绝对值=2,也就是隔着一行,那他就点亮隔着这一行,也能到达。

我们把每两个点之间相通用的金币算出来,就建一个图,每相通的两个点连边,权值就是花费的金币,然后直接最短路就好了。。这时候,如果两个相邻,很简单,连起来权值是0就好了,如果不相邻呢,一般想法是k2暴力建边,那样假如所有点都在两行上,每一行最多5e3个点,容易t也会mle,所以我们就要拆点了,把每行每列都规定一个入点,一个出点,每一行/列到这两个点的距离是0,连的时候把相应的入点出点连起来就好了,这样相当于那两个点连起来,还不用费空间,怎样连呢,前面说了花费是1的几种可能,总结下, 只要行或者列差值小于2,他就可以花费一个金币到达,所以我们也不用暴力任何两个点,因为我们已经把每行每列的出点入点跟这一行/列的所有点的权值都变成0了,所以,我们只需要暴力每个亮的点,把他所在行跟所在列周围的两行/两列连起来就好了。。k4的复杂度,在这之前把相邻点的边也建好,不用k2暴力,用一个map标记出现过得点,到时候暴力k个点,查找他周围四个点是否出现过,出现过就连起来就好了,k4复杂度~

代码:

#include <iostream>#include <cstring>#include <algorithm>#include <cstdio>#include <queue>#include <vector>#include <map>using namespace std;const int maxn = 5e4 + 5;const int INF = 1e9 + 5;int dir[4][2] = {0,1,0,-1,1,0,-1,0};struct node{    int x, y;    node(){}    node(int xx, int yy): x(xx), y(yy){}}a[maxn];vector<node> v[maxn];map<int , int> mp[maxn];int dis[maxn], book[maxn], n, m, k, star;;void spfa(int u)  //spfa{    memset(book, 0, sizeof(book));    for(int i = 1; i < maxn; i++) dis[i] = INF;    dis[u] = 0;    queue<int> q;    q.push(u);    while(!q.empty())    {        u = q.front();        q.pop();        book[u] = 0;        for(int i = 0; i < v[u].size(); i++)        {            int to = v[u][i].x, w= v[u][i].y;            if(dis[to] > dis[u]+w)            {                dis[to] = dis[u]+w;                if(!book[to])                {                    book[to] = 1;                    q.push(to);                }            }        }    }    int ans = INF;    for(int i = 1; i <= k; i++)     {        if(a[i].x == n && a[i].y == m)  //如果终点本来是亮着的,那就直接是到这个点的dis就好了            ans = min(ans, dis[i]);        if((n-a[i].x) <= 1 || (m-a[i].y) <= 1) //如果没亮着,只能跟在他某一行/列或者相邻行/列才行,相隔一行过不去,因为终点不是亮着的            ans = min(ans, dis[i]+1);  //这两种方式取一个最小值    }    if(ans == INF)        puts("-1");    else        printf("%d\n", ans);}int main(){    while(~scanf("%d%d%d", &n, &m, &k))    {        memset(book, 0, sizeof(book));        for(int i = 1; i <= maxn; i++)            mp[i].clear(), v[i].clear();        for(int i = 1; i <= k; i++)          {            scanf("%d%d", &a[i].x, &a[i].y);            mp[a[i].x][a[i].y] = i;  //map记录每个横纵坐标是第几个出现的,也就是第几个点            if(a[i].x == 1 && a[i].y == 1) //找出1,1点是第几个点,他肯定要亮着,否则走不到                star = i;        }        for(int i = 1; i <= k; i++)  //把相邻的点之间建好边        {            for(int j = 0; j < 4; j++)  //看他周围有亮着点吗            {                int tx = a[i].x + dir[j][0];                int ty = a[i].y + dir[j][1];                if(mp[tx][ty])                {                    v[mp[tx][ty]].push_back(node(i, 0));                    v[i].push_back(node(mp[tx][ty],0));                }            }        }        for(int i = 1; i <= k; i++)  //建立每行每列的出点入点        {             v[i].push_back(node(a[i].x+k, 0));            v[a[i].x+k+n].push_back(node(i, 0));            v[i].push_back(node(a[i].y+k+2*n, 0));            v[a[i].y+k+2*n+m].push_back(node(i, 0));        }        for(int i = 1; i <= n; i++)  //建立每个点到相邻2个以内的行列的边,通过与对应行列的入点出点建边        {            for(int j = -2; j <= 2; j++)            {                int tmp = i+j;                if(tmp >= 1 && tmp <= n)                {                    v[tmp+k].push_back(node(i+k+n,1));                    v[i+k].push_back(node(tmp+k+n,1));                }            }        }        for(int i = 1; i <= m; i++)        {            for(int j = -2; j <= 2; j++)            {                int tmp = i+j;                if(tmp >= 1 && tmp <= m)                {                    v[tmp+k+n*2].push_back(node(i+k+2*n+m,1));                    v[i+k+n*2].push_back(node(tmp+k+n*2+m,1));                }            }        }        spfa(star);    }    return 0;}

暴力代码- -:

#include <cstdio>#include <queue>#include <cstring>#include <algorithm>using namespace std;int rd() {    int x = 0; char c = getchar();    while (c > '9' || c < '0') c = getchar();    while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();    return x;}const int N = 1e4 + 10;const int inf = 0x7f7f7f7f;int dis[N], x[N], y[N], n, m, K;bool fg, vis[N];int spfa() {    queue <int> q;    memset(dis, 0x7f, sizeof dis);    q.push(1), vis[1] = 1, dis[1] = 0;    while (!q.empty()) {        int u = q.front(); q.pop();        for (int i = 1; i <= K; i ++) {            if (i == u) continue;            int w = inf;            int dx = abs(x[i] - x[u]), dy = abs(y[i] - y[u]);            if (dx + dy == 1) w = 0;            else if (dx <= 2 || dy <= 2) w = 1;            if (dis[i] > dis[u] + w) {                dis[i] = dis[u] + w;                if (!vis[i]) vis[i] = 1, q.push(i);            }         }        vis[u] = 0;    }    return dis[K] == inf ? -1 : dis[K];}int main() {    n = rd(), m = rd(), K = rd();    for (int i = 1; i <= K; i ++) {        x[i] = rd(), y[i] = rd();        fg |= (x[i] == n && y[i] == m);    }    if (!fg) x[++K] = n + 1, y[K] = m + 1;    printf("%d\n", spfa());    return 0;}


原创粉丝点击