POJ1436 线段树入门 区间标记

POJ1436 线段树入门

传送门
大概题意:
有n条垂直于x轴的线段,给定每条线段的 ‘y’ 范围和 'x’位置
当两条线段之间可以用一条不覆盖到其他线条且平行于X轴的线段连接时,定义两条线段相互见。求有多少 “两两可见的三条线段” 的数量。

思维:
首先把题目给的数据可视化一下(图有点丑)
图1
然后用肉眼观察(废话)
从左到右地看,发现只有 黄色 、蓝色 和 红色这一对能组成符合要求的三元组。
图2
这样子看好像看不出什么,所以把图倒过来看看吧,顺便想象一下他们会落下来。
图3

那么这里让比较靠近y轴的先落下来,我们会发现一些线段会压在其他线段上面,这时我们就把上面的线段用一个箭头指向下面的线段。
图4
我们发现当箭头①指向的线段上连着箭头③,而箭头①出发的线段也有箭头②指向了同样的线段,这时这三个线段就满足了“两两相互可见”
所以我们可以让线段一段一段地落下来,然后落下来的时候,统计一下它压到了哪些线条,这样子就可以在用循环找出有多少对符合条件的三元组了。

算法:
对所有线段根据x轴位置从小到大进行排序
对y轴范围构建线段树
对所有线段依x轴位置升序进行以下操作:

  • 查询当前线段y范围内压到了哪些线段,并记录下来
  • 更新当前线段y范围内的线段为当前线段

对所有记录压到谁的数据进行统计,得出答案(不愿详细写,还是康代码吧hhh)

实现一:

#include<stdio.h>
#include<vector>
#include<algorithm>
#include<string.h>
#define mid int m = (l+r)>>1;
#define lson l,m,loc<<1
#define rson m+1,r,loc<<1|1
using namespace std;

const int maxn = 20000;
//记录输入数据
struct Node{
    int y1,y2,x;
}data[maxn];
//定义比较函数,x轴的
bool operator<(const Node&a,const Node &b){
    return a.x<b.x;
}
//线段树本体
int t[maxn<<2];
//记录哪些线段压着哪些线段,有点像图的邻接表
vector<int> mark[maxn];
//这个用来防止重复记录和重复计数,看后面就明白了
int is[maxn];
//这个是答案
int ans;
//初始化函数
void init(){
    memset(t,-1,sizeof(t));
    ans = 0;
}
//下推函数,一般别人会写成PushDown
inline void Push(int loc){
    if(t[loc]==-1)return;
    t[loc<<1] = t[loc<<1|1] = t[loc];
    t[loc] = -1;
}
//段落更新函数,更改段落L到R的线段标记为V
void update(int L,int R,int V,int l,int r,int loc){
    if(L<=l&&r<=R){
        t[loc] = V;
        return;
    }
    Push(loc);
    mid;
    if(L<=m)update(L,R,V,lson);
    if(m<R)update(L,R,V,rson);
}
//查询函数,记录压着哪些线段了
void query(int L,int R,int V,int l,int r,int loc){
    if(L<=l&&r<=R&&t[loc]!=-1){
    //is[t[loc]]==V代表这条线段被V压着,如果压了两个同样标记的段落,只算一次
        if(is[t[loc]]!=V){
        mark[V].push_back(t[loc]);
        is[t[loc]] = V;
        }
        return;
    }
    if(l==r)return;
    Push(loc);
    mid;
    if(L<=m)query(L,R,V,lson);
    if(R>m)query(L,R,V,rson);
}
int main(){
//pp是测试个数,num是每个测试用例的输入行数,total...看我下面代码就好了,懒得改代码风格了
    int pp,num,total = 0;
    scanf("%d",&pp);
    while(pp--){
        init();
        scanf("%d",&num);
        total = 0;
        while(num--){
            scanf("%d%d%d",&data[total].y1,&data[total].y2,&data[total].x);
            total++;
        }
        //对输入数据根据X轴位置进行排序
        sort(data,data+total);
        //先查询,再覆盖,然后每个线段的标记都不一样,这个根据排序后的下标来就好了,另外区间可能产生冲突,所以乘以2解决这个矛盾,比如说
        //[3,6],[3,4],[5,6]重叠后,应该是有三段的,但实际上只记录了[3,4]和[5,6],(4,5)被吞掉了
        //将其乘以2后就解决了这个问题,自己算算看?手打累
        for(int i = 0;i<total;++i){
            mark[i+1].clear();
            query((data[i].y1+1)<<1,(data[i].y2+1)<<1,i+1,1,maxn,1);
            update((data[i].y1+1)<<1,(data[i].y2+1)<<1,i+1,1,maxn,1);
        }
        //这里重新利用了一下is数组,用来防止重复计数
        memset(is,0,sizeof(is));
        
        /*debug 显示记录的数据*/
      /*  for(int i = 1;i<total+1;++i){
            printf("%d:  ",i);
            for(int j = 0;j<mark[i].size();++j){
                printf("%d ",mark[i][j]);
            }
            printf("\n");
        }*/
        //对所有线段标记执行循环,从第二条开始循环,毕竟第一条只能压空气
        for(int i = 2;i<total+1;++i){
        //记录一下当前线段压到了哪些线段
            int len = mark[i].size();
            for(int j = 0;j<len;++j){
                is[mark[i][j]] = i;
            }
            //对每一个被当前线段压到的线段执行循环
            for(int j = 0;j<len;++j){
                int to = mark[i][j];
                int len = mark[to].size();
                for(int k = 0;k<len;++k){
                //如果这个线段有被当前线段压到的线段压到且当前线段也压到了这个线段
                //就说明出现了符合题意三元组
                //另外后边这个is语句好像没必要加上去hhh
                    if(is[mark[to][k]]&&is[mark[to][k]]!=to){
                        //记录一下算过这条线段了,防止重复计数,其实好像没必要的说,但是加上去好像更保险一些,说不定一不小心记录了两条相同的线段呢?
                        is[mark[to][k]] = to;
                        ++ans;
                    }
                }
            }
            //神奇的操作,我要取消记录当前线段压到了哪些线段
            for(int j = 0;j<len;++j){
                is[mark[i][j]] = false;
            }
        }
        //输出答案
        printf("%d\n",ans);
    }
    return 0;
}

实现二(自以为优化了,但其实好像没什么卵用,只说说优化的部分):

/*
==题意:有n条垂直于x轴的线段,给出他们y范围。
当两条线段之间有一条垂直于他们并且可以不经过其他线段,
则可定义两条线段互相可见,
求出有多少三条线段两两互相可见==
*/
#include<stdio.h>
#include<vector>
#include<algorithm>
#include<string.h>
#define mid int m = (l+r)>>1;
#define lson l,m,loc<<1
#define rson m+1,r,loc<<1|1
using namespace std;
const int maxn = 20000;
struct Node{
    int y1,y2,x;
}data[maxn];
bool operator<(const Node&a,const Node &b){
    return a.x<b.x;
}
int t[maxn<<2];
vector<int> mark[maxn];
int is[maxn];
int ans;
void init(){
    memset(t,-1,sizeof(t));
    ans = 0;
}
//t[loc]增加了一个状态0,代表当前区块内有更“细”的区间是被标记的
//比如说:[1,10]->[1,5],[1,10]的状态是0,[1,5]的状态是3
//而t[loc]==-1则代表当前段落完全没有被标记的线段
inline void Push(int loc){
    if(t[loc]!=-1&&t[loc]!=0){
    t[loc<<1] = t[loc<<1|1] = t[loc];
    }
    //有Push下面必有更细的区间
    t[loc] = 0;
}
void update(int L,int R,int V,int l,int r,int loc){
    if(L<=l&&r<=R){
        t[loc] = V;
        return;
    }
    Push(loc);
    mid;
    if(L<=m)update(L,R,V,lson);
    if(m<R)update(L,R,V,rson);
}
void query(int L,int R,int V,int l,int r,int loc){
    if(L<=l&&r<=R&&t[loc]!=-1&&t[loc]!=0){
        if(is[t[loc]]!=V){
        mark[V].push_back(t[loc]);
        is[t[loc]] = V;
        }
        return;
    }
    //这样子遇到-1状态就可以直接返回了
    if(l==r||t[loc]==-1)return;
    Push(loc);
    mid;
    if(L<=m)query(L,R,V,lson);
    if(R>m)query(L,R,V,rson);
}
int main(){
    int pp,num,total = 0;
    scanf("%d",&pp);
    while(pp--){
        init();
        scanf("%d",&num);
        total = 0;
        while(num--){
            scanf("%d%d%d",&data[total].y1,&data[total].y2,&data[total].x);
            total++;
        }
        sort(data,data+total);
        for(int i = 0;i<total;++i){
            mark[i+1].clear();
            query((data[i].y1+1)<<1,(data[i].y2+1)<<1,i+1,1,maxn,1);
            update((data[i].y1+1)<<1,(data[i].y2+1)<<1,i+1,1,maxn,1);
        }
        memset(is,0,sizeof(is));
        /*debug*/
/*
        for(int i = 1;i<total+1;++i){
            printf("%d:  ",i);
            for(int j = 0;j<mark[i].size();++j){
                printf("%d ",mark[i][j]);
            }
            printf("\n");
        }
*/
//另外去除了is的判断
        for(int i = 2;i<total+1;++i){
            int len = mark[i].size();
            for(int j = 0;j<len;++j){
                is[mark[i][j]] = i;
            }
            for(int j = 0;j<len;++j){
                int to = mark[i][j];
                int len = mark[to].size();
                for(int k = 0;k<len;++k){
                    if(is[mark[to][k]]){
                        ++ans;
                    }
                }
            }
            for(int j = 0;j<len;++j){
                is[mark[i][j]] = false;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

最后,感谢阅读

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值