1. 程式人生 > >Luogu2839 Middle 主席樹、二分答案

Luogu2839 Middle 主席樹、二分答案

blank getc name namespace ble 結構 ddl 分答 mes

題目傳送門:https://www.luogu.org/problemnew/show/P2839

題目大意:給出一個長度為$N$的序列與$Q$次詢問,每次詢問左端點在$[a,b]$,右端點在$[c,d]$的區間中最大的中位數,強制在線(本題中的中位數定義與平常不同,設某區間長度為$L$,則在從小到大排序後的序列中(編號從$0$開始),其中位數為第$\lfloor L/2 \rfloor$號元素)$N,Q \leq 2 \times 10^4$


這鬼題讓我知道主席樹可以用於除第$K$大以外的問題$qwq$

觀察$100 \%$的數據規模,$O(nQ)$的做法都比較吃力,所以考慮使用$log$數據結構進行維護獲得$O(Qlogn)$或者$O(Qlog^2n)$的算法。故考慮到使用線段樹進行維護,同時使用二分的方式尋找每個詢問的答案,

其中check的內容就是尋找是否有滿足該詢問條件的區間,在其中(大於等於當前二分的數的數字個數)要大於等於(小於當前二分的數的數字個數)。斷句略奇怪
不妨將大於等於當前二分的數的數字的權值設為1,小於當前二分的數的數字的權值設為-1,check的內容就等價於詢問$$max(\sum_{i=x}^y w_i) \geq 0 (x \in {[a , b]} , y \in{[c , d]})$$是否成立。

所以想到對於每個數字建立一個線段樹存儲權值,在每一次二分詢問時求出對應線段樹中$x \in {[a , b]} , y \in{[c , d]},\sum_{i=b+1}^{c-1} w_i + max(\sum_{i=x}^b w_i)+max(max(\sum_{i=c}^y w_i))$是否大於0,剛好這三個式子對應區間和、區間最大後綴、區間最大前綴,可以使用線段樹解決。
然後發現對於排序後的相鄰兩數只有一個$1$變成$-1$,就可以使用主席樹將空間壓到允許範圍內了
時間復雜度為$O(Qlog^2n)$,空間復雜度為$O(nlogn)$,符合本題數據範圍

  1 #include<bits/stdc++.h>
  2 #define MAXN 100002
  3 using namespace std;
  4 inline int read(){
  5     int a = 0;
  6     bool f = 0;
  7     char c = getchar();
  8     while(!isdigit(c)){
  9         if(c == -)
 10             f = 1;
 11         c = getchar();
 12     }
 13     while(isdigit(c)){
14 a = (a << 3) + (a << 1) + (c ^ 0); 15 c = getchar(); 16 } 17 return f ? -a : a; 18 } 19 char output[12]; 20 inline void print(int x){ 21 int dirN = 11; 22 if(x == 0) 23 fwrite("0" , sizeof(char) , 1 , stdout); 24 else{ 25 if(x < 0){ 26 x = -x; 27 fwrite("-" , sizeof(char) , 1 , stdout); 28 } 29 while(x){ 30 output[--dirN] = x % 10 + 48; 31 x /= 10; 32 } 33 fwrite(output + dirN , 1 , strlen(output + dirN) , stdout); 34 } 35 fwrite("\n" , 1 , 1 , stdout); 36 } 37 struct node{ 38 int sum , lMax , rMax , l , r; 39 }Tree[22 * MAXN]; 40 struct sortNum{//用於排序 41 int ind , num; 42 bool operator <(sortNum a){ 43 return num < a.num; 44 } 45 }sorted[MAXN]; 46 int num[MAXN] , root[MAXN]; 47 int N , cntNode = 1 , rMax , rSum , lMax , lSum; 48 49 inline int max(int a , int b){ 50 return a > b ? a : b; 51 } 52 53 inline void swap(int &a , int &b){ 54 int t = a; 55 a = b; 56 b = t; 57 } 58 59 //初始化一個所有葉子結點權值都為1的線段樹 60 void init(int dir , int l , int r){ 61 Tree[dir].sum = Tree[dir].lMax = Tree[dir].rMax = r - l + 1; 62 if(l != r){ 63 init(Tree[dir].l = ++cntNode , l , l + r >> 1); 64 init(Tree[dir].r = ++cntNode , (l + r >> 1) + 1 , r); 65 } 66 } 67 68 inline void pushup(int dir){ 69 Tree[dir].lMax = max(Tree[Tree[dir].l].lMax , Tree[Tree[dir].l].sum + Tree[Tree[dir].r].lMax); 70 Tree[dir].rMax = max(Tree[Tree[dir].r].rMax , Tree[Tree[dir].r].sum + Tree[Tree[dir].l].rMax); 71 Tree[dir].sum = Tree[Tree[dir].l].sum + Tree[Tree[dir].r].sum; 72 } 73 74 //更新版本 75 void update(int now , int last , int l , int r , int dir){ 76 if(l == r){ 77 Tree[now].lMax = Tree[now].rMax = 0; 78 Tree[now].sum = -1; 79 } 80 else{ 81 if(dir > l + r >> 1){ 82 Tree[now].l = Tree[last].l; 83 update(Tree[now].r = ++cntNode , Tree[last].r , (l + r >> 1) + 1 , r , dir); 84 } 85 else{ 86 Tree[now].r = Tree[last].r; 87 update(Tree[now].l = ++cntNode , Tree[last].l , l , l + r >> 1 , dir); 88 } 89 pushup(now); 90 } 91 } 92 93 //區間和 94 int findSum(int dir , int l , int r , int L , int R){ 95 if(L >= l && R <= r) 96 return Tree[dir].sum; 97 int sum = 0; 98 if(l <= L + R >> 1) 99 sum += findSum(Tree[dir].l , l , r , L , L + R >> 1); 100 if(r > R + L >> 1) 101 sum += findSum(Tree[dir].r , l , r , (L + R >> 1) + 1 , R); 102 return sum; 103 } 104 105 //區間最大後綴 106 void findRightMax(int dir , int l , int r , int L , int R){ 107 if(L >= l && R <= r){ 108 rMax = max(rMax , Tree[dir].rMax + rSum); 109 rSum += Tree[dir].sum; 110 return; 111 } 112 if(r > L + R >> 1) 113 findRightMax(Tree[dir].r , l , r , (L + R >> 1) + 1 , R); 114 if(l <= L + R >> 1) 115 findRightMax(Tree[dir].l , l , r , L , L + R >> 1); 116 } 117 118 //區間最大前綴 119 void findLeftMax(int dir , int l , int r , int L , int R){ 120 if(L >= l && R <= r){ 121 lMax = max(lMax , Tree[dir].lMax + lSum); 122 lSum += Tree[dir].sum; 123 return; 124 } 125 if(l <= L + R >> 1) 126 findLeftMax(Tree[dir].l , l , r , L , L + R >> 1); 127 if(r > L + R >> 1) 128 findLeftMax(Tree[dir].r , l , r , (L + R >> 1) + 1 , R); 129 } 130 131 //二分check 132 //為了方便處理這裏的代碼與上面的公式稍有不同 133 inline bool check(int mid , int a , int b , int c , int d){ 134 lSum = rSum = 0; 135 lMax = rMax = -1; 136 findRightMax(root[mid] , a , b - 1 , 1 , N); 137 findLeftMax(root[mid] , c + 1 , d , 1 , N); 138 return findSum(root[mid] , b , c , 1 , N) + lMax + rMax >= 0; 139 } 140 141 int main(){ 142 N = read(); 143 long long lastans = 0; 144 for(int i = 1 ; i <= N ; i++) 145 num[sorted[i].ind = i] = sorted[i].num = read(); 146 init(root[1] = 1 , 1 , N); 147 sort(sorted + 1 , sorted + N + 1); 148 for(int i = 1 ; i <= N ; i++) 149 update(root[i + 1] = ++cntNode , root[i] , 1 , N , sorted[i].ind); 150 for(int Q = read() ; Q ; Q--){ 151 int a = (read() + lastans) % N + 1 , b = (read() + lastans) % N + 1 , c = (read() + lastans) % N + 1 , d = (read() + lastans) % N + 1; 152 if(a > b) 153 swap(a , b); 154 if(a > c) 155 swap(a , c); 156 if(a > d) 157 swap(a , d); 158 if(b > c) 159 swap(b , c); 160 if(b > d) 161 swap(b , d); 162 if(c > d) 163 swap(c , d); 164 int l = 1 , r = N; 165 while(l < r){ 166 int mid = l + r + 1 >> 1; 167 if(check(mid , a , b , c , d)) 168 l = mid; 169 else 170 r = mid - 1; 171 } 172 printf("%d\n" , lastans = sorted[l].num); 173 } 174 return 0; 175 }

Luogu2839 Middle 主席樹、二分答案