hdu 5603 the soldier of love

18 篇文章 0 订阅
9 篇文章 0 订阅

传送门:
http://acm.hdu.edu.cn/showproblem.php?pid=5603

题意:
有n条线段,m个组,每个组中包含一部分点,求问对于每个组,有多少条线段包含组中至少一个点!!!

思路:
很神奇的解法!
一看分组很多且分组里面的点很多,既然总共的点数就是1到1e6,那么很自然的想法肯定就是扫面一遍这1e6个点,然后线性地把答案给算并存下来!

首先我们应该反着求,即求不覆盖这个分组中任何一点的线段一共有多少条。
那么可以这样,把无穷远和无穷近加到点的集合里面,记录一个分组中一个点的前驱节点,那么我们就可以求出在相邻两个节点之间的线段有多少条,加到这个分组里面就可以了!!

我们可以这样理解,用每条线段的左端点作为这条线段的标志,那么做法就是,每一次,记录右端点在相邻两个点之间的线段的数量,则在代表该条线段的左端点处打上标记(+1),
(只是这一个位置哦,不要被树状数组的求法给弄混淆了!!!)然后求一下前缀和就代表右端点小于当前点的线段有多少条,然后再一减就代表了在这两个点之间的线段有多少条!!!可以自己意会一下,因为减掉的是右端点小于上一个点的线段条数,所以目前剩下的就一定是位于这两个点之间的线段条数!!!

其实也就是相当于我们想求位于两个点之间的标记了的左端点的个数,这个不正好就可以去利用前缀和减么!!!!
就是相当于,我们利用给左端点打上标记的方法去,而且这样去求解不会重复,因为如果在一个时刻求出了右端点小于当前点的线段数量加了1,那么对于后一个时刻,这个新增的线段一定要被算在内,所以真的是完美地利用过了前缀和去解决这个问题!!!!

晕死了,肯定是要对右端点排序啊!!不然一定会漏加的!!

但实际上还是有点出入的,因为查询当前点的时候,它才代表的是右端点在当前点左边的线段数量,而过后的话,代表的就不是这个意义了,因为陆陆续续又有点被标记上了!!!

这才是这个解法和用前缀和的准确含义!!!
就是说现在有一堆点被打上了标记,然后过一轮之后又有一堆点被打上了标记,我现在要查询的就是在所有被打上标记的点中在指定区间内的点的数量(也就是说从所有右端点比当前点小的线段中找到符合条件的在区间内的点的数量)!!!!!

最后再准确地去说一遍,他的方法是这样的,找到所有右端点小于当前点的线段的左端点的位置标记数组,然后求出位置在两个点这间的的点的数量,这才是那个前缀和求解的真谛所在!!!!!!! 

code :

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
const int maxm=3e5+10;
struct node{
    int pos,pre,mark;
    node(int _pos=0,int _pre=0,int _mark=0){
        pos=_pos;pre=_pre;mark=_mark;
    }
}a[maxn];
struct edge{
    int l,r;
}e[maxn];
int ans[maxm];
int c[maxn+5];
bool cmp1(edge a,edge b) {return a.r<b.r;}
bool cmp2(node a,node b) {return a.pos<b.pos;}
void add(int x,int y){
    while(x<maxn){
        c[x]+=y;
        x+=(x&(-x));
    }
}
int query(int x){
    int ans=0;
    while(x>0){
        ans+=c[x];x-=(x&(-x));
    }  return ans;
}
int n,m,t,tot,ll,rr;
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        tot=0;memset(c,0,sizeof(c));
        memset(ans,0,sizeof(ans));
        for(int i=0;i<n;i++){
            scanf("%d%d",&e[i].l,&e[i].r);
        } int tmp,pos,pre;
        for(int i=1;i<=m;i++){
            scanf("%d",&tmp);pre=0;
            while(tmp--){
                scanf("%d",&pos);
                a[tot++]=node(pos,pre,i);pre=pos;
            }
            a[tot++]=node(maxn-1,pre,i);
        }int now=0;
        sort(e,e+n,cmp1);
        sort(a,a+tot,cmp2);
        for(int i=0;i<tot;i++){
            while(now<n&&e[now].r<a[i].pos) add(e[now].l,1),now++;
            ans[a[i].mark]+=query(a[i].pos)-query(a[i].pre);
        }
        for(int i=1;i<=m;i++){
            printf("%d\n",n-ans[i]);
        }
    }   
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值