1. 程式人生 > >皮卡丘的夢想2(線段樹+二進制狀態壓縮)

皮卡丘的夢想2(線段樹+二進制狀態壓縮)

pac ane idg rip 實現 而且 space 存在 引入

Description

一天,一只住在 501 實驗室的皮卡丘決定發奮學習,成為像 LeiQ 一樣的巨巨,於是他向鎮上的賢者金桔請教如何才能進化成一只雷丘。

金桔告訴他需要進化石才能進化,並給了他一個地圖,地圖上有 n 個小鎮,他需要從這些小鎮中收集進化石。

接下來他會進行 q 次操作,可能是打聽進化石的信息,也可能是向你詢問第 l 個小鎮到第 r 個小鎮之間的進化石種類。

如果是打聽信息,則皮卡丘會得到一個小鎮的進化石變化信息,可能是引入了新的進化石,也可能是失去了全部的某種進化石。

如果是向你詢問,你需要回答他第 l 個小鎮到第 r 個小鎮之間的進化石種類。

Input

首先輸入一個整數 T (1 <= T <= 10),代表有 T 組數據。

每組數據的第一行輸入一個整數 n (1 <= n <= 100000) 和一個整數 q (1 <= q <= 100000),分別代表有 n 個小鎮,表皮卡丘有 q 次操作。

接下來輸入 q 行,對於每次操作,先輸入操作類型,然後根據操作類型讀入:

  • 1: 緊接著輸入 2 個整數 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 個小鎮引入了第 b 種進化石
  • 2: 緊接著輸入 2 個整數 a (1 <= a <= n), b (1 <= b <= 60),表示第 a 個小鎮失去了全部第 b 種進化石
  • 3: 緊接著輸入 2 個整數 l, r (1 <= l <= r <= n),表示他想詢問從第 l 個到第 r 個小鎮上可收集的進化石有哪幾種

Output

對於每組輸入,首先輸出一行 "Case T:",表示當前是第幾組數據。

對於每組數據中的每次 3 操作,在一行中按編號升序輸出所有可收集的進化石。如果沒有進化石可收集,則輸出一個 MeiK 的百分號 "%"(不包括引號)。

Sample Input

1
10 10
3 1 10
1 1 50
3 1 5
1 2 20
3 1 1
3 1 2
2 1 50
2 2 20
3 1 2
3 1 10

Sample Output

Case 1:
%
50
50
20 50
%
%

解題思路:剛好暑假學完了線段樹,我一開始就把這道題當做線段樹的裸題,用線段樹來維護數據,不就是單點更新,區段查詢嘛。但是很不幸時間超限,後來我考慮了原來每次查詢進化石的時候還是要全部遍歷一遍,所以是要超時的。這時我們就需要更高效的方法來存儲和查詢所有進化石的狀態,這種方法就是利用二進制進行狀態壓縮。

我們用二進制數的每一位來表示每種進化石的有無,也即該小鎮進化石的存在狀態。例如,對於有 1、4 號進化石的小鎮,我們可以用二進制數 1001 來表示。它的含義是:從右向左依次表示第 1 個到第 n 個進化石的有無,1 表示有,0 表示無。而且很容易想到,由於每一位只有 0 或 1 兩種可能,且每一位都對應固定的編號,所以對於任意一個二進制數,都能保證唯一對應一種存在狀態。

解決了如何表示存在狀態的問題,下一步就是如何存儲了。例如,當前我們的進化石存在狀態為:1、4,對應二進制 1001,如果我們加入一個 3 號進化石,則應變為 1101,也就是讓倒數第三位變成 1。這裏需要用到位運算:對於 1001,我們讓它與 0100(只含有 3 號石的狀態)進行或運算,即兩數對應的位有一個或兩個為 1 時結果為1,否則為 0,運算結果為 1101。這樣我們使用或運算就可以實現兩個狀態的合並。至於如何表示單個進化石的狀態,很簡單,使用左移運算就可以了,例如:表示 3 號石存在,只需將 1(0001)左移 3-1=2 位即得到 0100。

這樣,我們只需要把二進制和線段樹結合一下就可以愉快地告別 TLE 了。在存儲時,每一個結點都表示它的左右子結點的合並狀態,即對左右子結點進行或運算後的結果,而葉結點直接存儲狀態。在查詢時,只需要遍歷結果對應二進制的每一位來輸出即可。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #define ll long long int
  5 const int MAXN=1e5+10;
  6 using namespace std;
  7 int n,q;
  8 ll sum[MAXN<<2];
  9 void push_up(int i)///向上回溯,狀態合並
 10 {
 11     sum[i]=sum[i<<1]|sum[i<<1|1];
 12 }
 13 void build(int l,int r,int rt)///建樹
 14 {
 15     if(l==r)
 16     {
 17         sum[rt]=0;
 18         return ;
 19     }
 20     int mid=(r+l)>>1;
 21     build(l,mid,rt<<1);
 22     build(mid+1,r,rt<<1|1);
 23     push_up(rt);
 24 }
 25 void add(int l,int r,int pos,int v,int rt)
 26 {
 27     if(l==r)
 28     {
 29         sum[rt]|=1ll<<(v-1);///1ll為長整型的1
 30         return ;
 31     }
 32     int m=(r+l)>>1;
 33     if(m>=pos)
 34     {
 35         add(l,m,pos,v,rt<<1);
 36     }
 37     else
 38     {
 39         add(m+1,r,pos,v,rt<<1|1);
 40     }
 41     push_up(rt);
 42 }
 43 void del(int l,int r,int pos,int val,int rt)
 44 {
 45     if(l==r)
 46     {
 47         sum[rt]&=~(1ll<<(val-1));
 48         return ;
 49     }
 50     int mid=(r+l)>>1;
 51     if(pos<=mid)
 52     {
 53         del(l,mid,pos,val,rt<<1);
 54     }
 55     else
 56     {
 57         del(mid+1,r,pos,val,rt<<1|1);
 58     }
 59     push_up(rt);
 60 }
 61 ll query(int l,int r,int L,int R,int rt)///區間查詢
 62 {
 63     if(L<=l&&R>=r)
 64     {
 65         return sum[rt];
 66     }
 67     ll ans=0;
 68     int mid=(r+l)>>1;
 69     if(L<=mid)
 70     {
 71         ans|=query(l,mid,L,R,rt<<1);
 72     }
 73     if(R>mid)
 74     {
 75         ans|=query(mid+1,r,L,R,rt<<1|1);
 76     }
 77     return ans;
 78 }
 79 
 80 int main()
 81 {
 82     int i,t;
 83     int op,x,y;
 84     scanf("%d",&t);
 85     for(i=1; i<=t; i++)
 86     {
 87         printf("Case %d:\n",i);
 88         scanf("%d%d",&n,&q);
 89         build(1,n,1);
 90         while(q--)
 91         {
 92             scanf("%d",&op);
 93             scanf("%d%d",&x,&y);
 94             if(op==1)
 95             {
 96                 add(1,n,x,y,1);
 97             }
 98             if(op==2)
 99             {
100                 del(1,n,x,y,1);
101             }
102             if(op==3)
103             {
104                 ll ans=query(1,n,x,y,1);
105                 ll cnt=0;
106                 int flag=1;
107                 int ot=1;
108                 while(ans)
109                 {
110                     if(ans&1)
111                     {
112                         if(flag)
113                         {
114                             flag=0;
115                         }
116                         else
117                         {
118                             printf(" ");
119                         }
120                         printf("%d",ot);
121                     }
122                     ot++;
123                     ans>>=1;
124                 }
125                 if(flag)
126                 {
127                     printf("%%\n");
128                 }
129                 else
130                 {
131                     printf("\n");
132                 }
133             }
134         }
135     }
136     return 0;
137 }

皮卡丘的夢想2(線段樹+二進制狀態壓縮)