HDU 6681(树状数组统计平面内射线的交点个数)

本文介绍了如何利用树状数组高效地解决一道关于统计平面内射线交点个数的问题,通过欧拉公式理解联通块个数与交点的关系,并详细解释了利用排序和双指针优化实现O(n)复杂度的解决方案。
摘要由CSDN通过智能技术生成

HDU 6681(树状数组,统计平面内射线的交点个数)

题目链接:传送门

题意:给出k条射线,求射线将 n ∗ m n*m nm 的区域分成几个联通块。每两条射线的端点x坐标和y坐标都互不相同。

思路:根据 欧拉公式 可以推导出联通块的个数等于射线的焦点个数c+1。但其实赛场上根本不知道这个定理,但有个很明显的道理,对于每条竖线,每条横着的射线与该竖线相交都会使联通块个数+1.(注意因为题目限制,这个射线的端点不会在边界上,不然在边界的射线会使联通块+2)。所以我们直接统计出射线的交点个数即可,这是个经典问题(但是我当时不会

​ 其实对于每条竖着的射线(假设端点为 x 0 , y 0 ​ x0,y0​ x0,y0),只要能统计出它左边横着的射线与之相交的射线个数和,即相交的射线的端点 x , y ​ x,y​ x,y满足射线方向向右且 x &lt; x 0 ​ x&lt;x0​ x<x0,y在射线( x 0 , y 0 ​ x0,y0​ x0,y0)纵坐标覆盖范围之内即可。统计左边横着的射线与之相交的射线个数也是同样道理

​ 因为射线的个数 k k k的范围为 1 e 5 ​ 1e5​ 1e5,故使用二维前缀和肯定是不行的。

​ 我们可以分类统计,我们先来统计每条射线左边横着的射线与之相交的射线个数,我们可以先将所有竖直方向的射线按 x x x从小到大排序,所有横着的射线按 x x x从小到大排序,对于第 i i i 条竖直方向的射线,我们可以处理所有左边的横着向右的射线,然后把他们的 y y y坐标加入树状数组 (这里需要对 y y y坐标进行离散化)。然后可以 O ( l o g n ) O(logn) O(logn)统计出树状数组内 y y y在第一条射线纵坐标覆盖范围之内的点的个数。双指针优化后可达到O(n)统计的效果。

欧拉公式:欧拉公式介绍

代码:

#include<bits/stdc++.h>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long u64;
typedef pair<int,int> P;
const int inf=0x3f3f3f3f;
const int N=1e5+100;
const int L=0,R=1,U=2,D=3;
ll X[N],Y[N],kinds[N],n,m,K,pes[N];
ll bt[N];
ll maxm;
struct node
{
    ll x,y,kind;
    node() {}
    node(ll x,ll y,ll kind):x(x),y(y),kind(kind) {}


    bool operator <(const node& other) const
    {
        return x<other.x;
    }
};
vector<node> row,col;
void disperse(ll X[],ll n)
{
    ll cnt=0;
    for(ll i=1; i<=n; ++i)
        pes[cnt++]=X[i];
    sort(pes,pes+cnt);
    cnt=unique(pes,pes+cnt)-pes;
    for(ll i=1; i<=n; ++i)
    {
        X[i]=lower_bound(pes,pes+cnt,X[i])-pes+1;
    }
}
ll lowbit(ll k)
{
    return k&-k;
}
void add(ll k,ll val)
{
    while(k<=maxm)
    {
        bt[k]+=val;
        k+=lowbit(k);
    }
}
ll get(ll k)
{
    ll ans=0;
    while(k)
    {
        ans+=bt[k];
        k-=lowbit(k);
    }
    return ans;
}
ll calc(ll l,ll r)
{
    return get(r)-get(l-1);
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll t;
    cin>>t;
    while(t--)
    {
        cin>>n>>m>>K;
        for(ll i=1; i<=K; ++i)
        {
            char c;
            cin>>X[i]>>Y[i]>>c;
            switch (c)
            {
            case 'L':
                kinds[i]=L;
                break;
            case 'R':
                kinds[i]=R;
                break;
            case 'U':
                kinds[i]=U;
                break;
            case 'D':
                kinds[i]=D;
                break;
            }
        }
        Y[K+1]=m;
        //离散化
        disperse(Y,K+1);
        maxm=Y[K+1];
        row.clear(),col.clear();
        //加入行列数组
        for(ll i=1; i<=K; ++i)
        {
            if(kinds[i]==L||kinds[i]==R)
            {
                row.push_back({X[i],Y[i],kinds[i]});
            }
            else
            {
                col.push_back({X[i],Y[i],kinds[i]});
            }
        }
        sort(row.begin(),row.end());
        sort(col.begin(),col.end());
        mset(bt,0);
        int p=0;
        ll ans=0;
        //进行双指针并树状数组维护信息
        /*统计每条竖线与左侧射线的交点个数*/
        for(ll i=0ll; i<col.size(); ++i)
        {
            ll x=col[i].x,y=col[i].y;
            while(p<row.size()&&row[p].x<x)
            {
                if(row[p].kind==R)
                    add(row[p].y,1);
                ++p;
            }
            if(col[i].kind==U)
                ans+=calc(y,maxm);
            else
                ans+=calc(1,y);
        }
        /*统计每条竖线与右侧射线的交点个数*/
        mset(bt,0);
        p=row.size()-1;
        for(ll i=col.size()-1ll; ~i; --i)
        {

            ll x=col[i].x,y=col[i].y;
            while(p>=0&&row[p].x>x)
            {
                if(row[p].kind==L)
                    add(row[p].y,1);
                --p;
            }

            if(col[i].kind==U)
                ans+=calc(y,maxm);
            else
                ans+=calc(1,y);
        }
        cout<<ans+1<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值