1. 程式人生 > >hdu3577(線段樹+lazy+樣例解釋+程式碼解析)

hdu3577(線段樹+lazy+樣例解釋+程式碼解析)

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。

程式碼解析:

#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;
}