4202. 穿过圆(倍增、LCA、bitset)——AcWing,第31场周赛

该博客讨论了在二维平面上n个点和m个圆的几何问题,涉及从一个点到另一个点的路径中最少穿过多少个圆。两种不同的解决方案被提出:bitset做法和倍增求LCA解法。前者通过位运算计算点与圆的状态,后者利用倍增算法求最近公共祖先来确定路径。博客提供了详细的代码实现和数据范围,适合对算法和数据结构感兴趣的读者深入理解。
摘要由CSDN通过智能技术生成

穿过圆

在一个二维平面上有 n 个点和 m 个圆。

点的编号为 1∼n。

不存在某个点恰好在某个圆的边上的情况。

任意两个圆之间没有公共点。

现在,请你回答 k 个询问。

每个询问给定两个点 ai,bi,并请你回答从点 ai 出发沿任意连续路径到达点 bi,至少需要穿过多少个圆。

输入格式
第一行包含三个整数 n,m,k。

接下来 n 行,其中第 i 行包含两个整数 xi,yi,表示点 i 的坐标为 (xi,yi)。注意,点的位置可以重合。

再接下来 m 行,其中第 i 行包含三个整数 ri,cxi,cyi,表示第 i 个圆的半径为 ri,圆心坐标为 (cxi,cyi)。

最后 k 行,每行包含两个整数 ai,bi,表示一个询问。注意,ai 可以等于 bi。

输出格式
共 k 行,第 i 行输出第 i 个询问的答案,即最少需要穿过的圆的数量。

数据范围
前三个测试点满足 1≤n,m,k≤10。
所有测试点满足 1≤n,m≤1000,1≤k≤105,−109≤xi,yi,cxi,cyi≤109,1≤ri≤109,1≤ai,bi≤n。

输入样例1:
2 1 1
0 0
3 3
2 0 0
1 2
输出样例1:
1
输入样例2:
2 3 1
0 0
4 4
1 0 0
2 0 0
3 0 0
1 2
输出样例2:
3

bitset做法题解

遍历n个点,每个点对m个圆有在圆内和圆外两个状态,园内表示0,圆外表示1,把状态用二进制存储下来
如果a点,b点两个点,a在圆内b在圆外,或者a在圆外b在圆内,那么这个圆是一定需要穿过的
从a点到b点最少需要穿过的圆数等于,两个状态的异或后的1的数量

#include <iostream>
#include <cstring>
#include <algorithm>
#include <bitset>
#include <cstdio>
#define x first
#define y second
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int N = 1010;

int n,m,Q;
PII p[N],c[N];
int r[N];

bitset<N> st[N];

LL sqr(LL x){
    return x*x;
}
int check(int a,int b)
{
    LL dx=p[a].x-c[b].x;
    LL dy=p[a].y-c[b].y;
    if(sqr(dx)+sqr(dy)>sqr(r[b])) 
        return 1;
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    for(int i=1; i<=n; ++i)
        scanf("%d%d",&p[i].x,&p[i].y);
    for(int i=0; i<m; ++i){
        scanf("%d%d%d",&r[i],&c[i].x, &c[i].y);
    }
    //遍历n个点,每个点对m个圆有在圆内和圆外两个状态,园内表示0,圆外表示1,把状态用二进制存储下来
    //如果a点b点两个点,a在圆内b在圆外,或者a在圆外b在圆内,那么这个圆是一定需要穿过的
    //从a点到b点最少需要穿过的圆数等于,两个状态的异或后的1的数量

    for(int i=1; i<=n; ++i)
        for(int j=0; j<m; ++j){
            st[i][j]=check(i,j);
        }
        
    while(Q--){
        int a,b;
        scanf("%d%d",&a,&b);
        
        printf("%d\n",(st[a]^st[b]).count());
    }
}

倍增求LCA解法 O(n2+klogn)

这个代码转载于原题解


#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 1010, M = 10;

int n, m, Q;
PII point[N];
int h[N], e[N], ne[N], idx;
int q[N], depth[N], fa[N][M];
int bel[N];

struct Circle
{
    int x, y, r;
}cir[N];

LL sqr(LL x)
{
    return x * x;
}

bool check(PII p, Circle c)
{
    LL dx = p.x - c.x;
    LL dy = p.y - c.y;
    if (sqr(dx) + sqr(dy) < sqr(c.r)) return true;
    return false;
}

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void bfs(int root)  // 预处理倍增数组
{
    memset(depth, 0x3f, sizeof depth);
    depth[0] = 0, depth[root] = 1;  // depth存储节点所在层数
    int hh = 0, tt = 0;
    q[0] = root;
    while (hh <= tt)
    {
        int t = q[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (depth[j] > depth[t] + 1)
            {
                depth[j] = depth[t] + 1;
                q[ ++ tt] = j;
                fa[j][0] = t;  // j的第二次幂个父节点
                for (int k = 1; k <= 9; k ++ )
                    fa[j][k] = fa[fa[j][k - 1]][k - 1];
            }
        }
    }
}

int lca(int a, int b)  // 返回a和b的最近公共祖先
{
    if (depth[a] < depth[b]) swap(a, b);
    for (int k = 9; k >= 0; k -- )
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    if (a == b) return a;
    for (int k = 9; k >= 0; k -- )
        if (fa[a][k] != fa[b][k])
        {
            a = fa[a][k];
            b = fa[b][k];
        }
    return fa[a][0];
}


int main()
{
    scanf("%d%d%d", &n, &m, &Q);
    for (int i = 1; i <= n; i ++ ) scanf("%d%d", &point[i].x, &point[i].y);
    for (int i = 1; i <= m; i ++ )
        scanf("%d%d%d", &cir[i].r, &cir[i].x, &cir[i].y);

    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            if (check(point[i], cir[j]))
                if (!bel[i] || cir[bel[i]].r > cir[j].r)
                    bel[i] = j;

    for (int i = 1; i <= n; i ++ )
        if (!bel[i])
            bel[i] = m + 1;

    for (int i = 1; i <= m; i ++ )
    {
        int t = 0;
        for (int j = 1; j <= m; j ++ )
            if (cir[j].r > cir[i].r && check({cir[i].x, cir[i].y}, cir[j]))
                if (!t || cir[t].r > cir[j].r)
                    t = j;
        if (!t) add(m + 1, i);
        else add(t, i);
    }

    bfs(m + 1);

    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        a = bel[a], b = bel[b];
        int p = lca(a, b);
        printf("%d\n", depth[a] + depth[b] - depth[p] * 2);
    }

    return 0;
}


作者:yxc
链接:https://www.acwing.com/activity/content/code/content/2196548/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

葛济维的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值