hdu3577(线段树+lazy+样例解释+代码解析)

40 篇文章 3 订阅
26 篇文章 0 订阅

Description

Chinese always have the railway tickets problem because of its' huge amount of passangers and stations. Now goverment need you to develop a new tickets query system. 
One train can just take k passangers. And each passanger can just buy one ticket from station a to station b. Each train cannot take more passangers any time. The one who buy the ticket earlier which can be sold will always get the ticket. 

Input

The input contains servel test cases. The first line is the case number. In each test case: 
The first line contains just one number k( 1 ≤ k ≤ 1000 ) and Q( 1 ≤ Q ≤ 100000 ) 
The following lines, each line contains two integers a and b, ( 1 ≤ a < b ≤ 1000000 ), indicate a query. 
Huge Input, scanf recommanded.

Output

For each test case, output three lines: 
Output the case number in the first line. 
If the ith query can be satisfied, output i. i starting from 1. output an blank-space after each number. 
Output a blank line after each test case.

Sample Input

1

3 6

1 6

1 6

3 4

1 5

1 2

2 4

Sample Output

Case 1:

1 2 3 5

题意:由于中国庞大的人口和站台,总是出现票的问题,现在政府需要你去开发一个新的查票系统。

一个火车只能载k个乘客,并且每个乘客仅仅只能从a->b买一张票,在任何时间每辆火车都不能载

更多的乘客。一个人提前买的票将是有效的

输入:

多组测试数据,第一行测试组数,接下来的每行有两个数字ab

输出:

每组测试数据输出三行,第一行测试组数,如果第i次查询满足题意输出从1i,每个数字有一个

空格,每组测试后有一个空行

解释样例:1->6  1-> 6 已经占了两个座位,3->43站台可以上车,1->5由于3->4经过的站与其有重复并且3->4先买的票,所以1->5不满足条件,1->2由于之前只有两个1->6经过1->2站,所以刚好可以左上车,2->41->5同理,因为与3->4有重复。

思路:看见这道题,有必要来复习一下线段树以及lazy思想。

线段树:线段树是一种二叉搜索数,每一个节点都对应一定的区间,能够快速的对区间进行更新,时间复杂度比较小;

Lazy思想:常运用于线段树的一个算法是lazy思想,lazy思想是说若更新的区间已经完全包含区间s,将s区间标记,暂不向下更新,若下一次的更新或询问需要用的已经标记过的区间的子区间,再将标记过得区间进行向下更新,并且取消对区间s的标记,增加对区间两个左右子树的标记。若一直未询问到,则不向下更新。通过这种方式可以达到节约时间的目的。

举个简单粗暴的例子:对应下面的那个图,假如目的是求和,现在要给[1,6] 的值都加2,那么我们从[1,12]->[1,6],然后[1,6]的sum值加上区间长度[ (6-1+1)*2 ],再把[1,6]的add[i]设置为2,就不再往下更新了【这里极大提高效率】。下一次更新/查询[1,6]的子区间时,我们将[1,6]原存的add值下传给[1,6]的两个直接子区间,再往下更新。假设在这种情况下,我们再更新[1,6]加3,则[1,6]的add值为2+3=5,然后我们查询[1,3],则从上往下经过[1,6]时把[1,6]的add值给了子区间[1,3]和[4,6],同时把sum[子区间]跟着子区间长度和add[父结点]改动,清除add[父节点]。【如果是查询间接子区间,则连续传递add值,也就是连续pushDown】详细例子:假设update()是区间改值,query()是求和,所有叶子区间的和都为1,则[7,8]和[7,9]在build()的时候就附上了值(图中绿色字体)。假设此时我们更新[7,9]的值,改为2,则线段树从[1,12]->[7,12]->[7,9],然后把[7,9]打上值为2的标记,求和(求和直接用区间长度*此时更新的值),然后不去更新[7,8]和[9,9]了,他们值仍然是2和1,lazy值为0。

链接:http://www.douban.com/note/273509745/这里有简单的介绍Lazy思想,不懂的可以去看看;

代码解析:

#include<iostream>
#include<string>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1000005;
int ans[N];
struct node
{
    int l,r,v,lazy;
}node[N<<2];    //  线段树的空间大概是数组空间的4倍;
void build(int l,int r,int numb)    //  线段树的建立;
{
    node[numb].l=l;
    node[numb].r=r;
    node[numb].v=0;
    node[numb].lazy=0;              //  用了lazy思想,提高了效率;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(l,mid,numb<<1);
    build(mid+1,r,numb<<1|1);
}
void PushUp(int numb)               //  往上往父节点方向更新数据;但是这里不是左右儿子的和,而是最大值,因为是站台人数;
{
    node[numb].v=max(node[numb<<1].v,node[numb<<1|1].v);
}
void PushDown(int numb)             //  向下往左右儿子方向更新数据;
{
    node[numb<<1].lazy+=node[numb].lazy;
    node[numb<<1|1].lazy+=node[numb].lazy;
    node[numb<<1].v+=node[numb].lazy;
    node[numb<<1|1].v+=node[numb].lazy;
    node[numb].lazy=0;              //  更新完了要清零;
}
void Insert(int l,int r,int numb)   //  插入更新数据;
{
    if(node[numb].l==l&&node[numb].r==r)    //  如果区间完全重合,则不需要再往下更新了,先保存起来,可以节约很多的时间(lazy思想)
    {
        node[numb].v+=1;
        node[numb].lazy+=1;
        return;
    }
    if(node[numb].lazy) PushDown(numb);     //  因为没有找到完全重合的区间,所以要先更新下一层区间;
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid) Insert(l,r,numb<<1|1);
    else if(r<=mid) Insert(l,r,numb<<1);
    else{
        Insert(l,mid,numb<<1);
        Insert(mid+1,r,numb<<1|1);
    }
    PushUp(numb);       //  最后还得往上返回,更新父节点区间;
}
int query(int l,int r,int numb)     //  查询区间l到r;
{
    if(node[numb].l==l&&node[numb].r==r){
        return node[numb].v;
    }
    if(node[numb].lazy) PushDown(numb);     //  道理同48行;
    int mid=(node[numb].r+node[numb].l)>>1;
    if(l>mid) return query(l,r,numb<<1|1);
    else if(r<=mid) return query(l,r,numb<<1);
    else{
        return max(query(l,mid,numb<<1),query(mid+1,r,numb<<1|1));  //  道理同28行;
    }
}
int main()
{
    int t,Case=1,len=0,k,m,a,b;
    scanf("%d",&t);
    while(t--){
        len=0;
        memset(ans,0,sizeof(ans));
        scanf("%d%d",&k,&m);
        build(1,1000000,1);
        for(int i=0;i<m;i++){
            scanf("%d%d",&a,&b);
            b--;                    //  这里有一个问题,就是乘客从a上车,b下车,所以乘客在车上的区间为(a,b--);
            if(query(a,b,1)<k){     //  表示可以上车;
                ans[len++]=i+1;
                Insert(a,b,1);
            }
        }
        printf("Case %d:\n",Case++);
        for(int i=0; i<len; i++)    //  格式问题害我又WA了一次;
            printf("%d ",ans[i]);
        printf("\n\n");
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值