BZOJ 4242 水壶

最小生成树+倍增

发现如果水壶是定值,那地图上肯定存在许多以建筑物为中心的联通块。并且随着水壶的增大,联通块大小也增大,有一些联通块会融合。

因此考虑把所有建筑物丢到队列搞BFS。如果两个联通块碰到了,就给这两个建筑物连上一条边。如果两个联通块代表的建筑之间已经存在一条路径可达,就不连。这样连出来是一棵树,而且也是最小生成树。

然而这样是的,有一个反例,因为对于同一个距离,队列更新有先后顺序。先更新的点距离远,后更新的点可能先碰到先更新的点导致连边错误。然后我发现只要不管三七二十一,是边直接连就行了,然后再求最小生成树即可。边数至多 O(hw4)

下午给自己的电脑装了ubuntu,用起来有点卡。。。好气

#include<cstdio>
#include<algorithm>
#define H 2005
#define N 200005
#define B 25
using namespace std;
namespace runzhe2000
{
    struct item{int x, y, belong, v;}que[H*H];
    struct edge{int next, to, val;}e[N<<1];
    struct pdge{int a, b, c;}pe[H*H];
    bool operator < (pdge a, pdge b){return a.c < b.c;}
    char s[H][H];
    int h, w, p, q, head, tail, ecnt, pecnt, f[N], fa[N][B], fv[N][B], last[N], bel[H][H], val[H][H], dep[N];
    int dx[4] = {0,0,1,-1}, dy[4] = {1,-1,0,0};
    int find(int x){return f[x] == x ? x : f[x] = find(f[x]);}
    void addedge(int a, int b, int c){e[++ecnt] = (edge){last[a], b, c}; last[a] = ecnt;}
    void addedge2(int a, int b, int c){addedge(a,b,c); addedge(b,a,c);}
    void addedge0(int a, int b, int c){pe[++pecnt] = (pdge){a,b,c};}
    void dfs(int x)
    {
        dep[x] = dep[fa[x][0]] + 1;
        for(int i = 1; i < B; i++) fa[x][i] = fa[fa[x][i-1]][i-1], fv[x][i] = max(fv[fa[x][i-1]][i-1], fv[x][i-1]);
        for(int i = last[x]; i; i = e[i].next)
        {
            int y = e[i].to;
            if(y != fa[x][0]) 
            {
                fa[y][0] = x;
                fv[y][0] = e[i].val;
                dfs(y);
            }
        }
    }
    int ask_lca(int a, int b)
    {
        if(dep[a] < dep[b]) swap(a, b);
        for(int d = dep[a] - dep[b], i = 0; i < B; i++)
            if(d & (1<<i)) a = fa[a][i];
        for(int i = B-1; ~i; i--)
            if(fa[a][i] != fa[b][i])
                a = fa[a][i], b = fa[b][i];
        return a == b ? a : fa[a][0];
    }
    int ask_max(int b, int a)
    {
        int r = 0, d = dep[a] - dep[b];
        for(int i = B-1; ~i; i--)
            if(d & (1<<i)) r = max(r, fv[a][i]), a = fa[a][i];
        return r;
    }
    void main()
    {
        scanf("%d%d%d%d",&h,&w,&p,&q);
        for(int i = 1; i <= h; i++)
            scanf("%s",s[i]+1);
        for(int i = 1, x, y; i <= p; i++)
        {
            scanf("%d%d",&x,&y);
            que[tail++] = (item){x, y, i, 0};
            bel[x][y] = i;
        }
        for(; head < tail; head++)
        {
            int x = que[head].x, y = que[head].y;
            for(int k = 0; k < 4; k++)
            {
                int nx = x + dx[k], ny = y + dy[k];
                if(nx < 1 || ny < 1 || nx > h || ny > w || s[nx][ny] == '#') continue;
                if(bel[nx][ny] && bel[nx][ny] != bel[x][y])
                    addedge0(que[head].belong, bel[nx][ny], val[nx][ny] + que[head].v);
                else if(!bel[nx][ny])
                {
                    bel[nx][ny] = que[head].belong;
                    val[nx][ny] = que[head].v + 1;
                    que[tail++] = (item){nx, ny, bel[nx][ny], val[nx][ny]};
                }
            }
        }
        sort(pe+1, pe+1+pecnt);
        for(int i = 1; i <= p; i++) f[i] = i;
        for(int i = 1; i <= pecnt; i++)
        {
            int f1 = find(pe[i].a), f2 = find(pe[i].b);
            if(f1 == f2) continue;
            f[f1] =f2;
            addedge2(pe[i].a,pe[i].b,pe[i].c);
        }

        for(int i = 1; i <= p; i++)
            if(!fa[i][0]) dfs(i);
        for(int i = 1, a, b; i <= q; i++)
        {
            scanf("%d%d",&a,&b);
            if(find(a) != find(b))
                puts("-1");
            else
            {
                int c = ask_lca(a, b), ans = 0;
                ans = max(ans, ask_max(c, a));
                ans = max(ans, ask_max(c, b));
                printf("%d\n",ans);
            }
        }
    }   
}
int main()
{
    runzhe2000::main();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值