2018.2.12 省選模擬賽
題目大意
(題目很簡潔了,不需要大意)
其實顯而易見地可以發現,當被卡一次後後面的路程都是固定了的。
可以用類似動態規劃的思想來進行預處理。現在的問題就是怎麽知道在某個位置剛等完紅燈然後出發會在哪個路口再次被卡。
嘗試畫一畫圖:
其中橫軸表示位置,縱軸表示時間,長方體表示紅燈時段。有用的部分長度只有$r + g$,所以在模意義下弄一下就可以減少很多重復和無用狀態:
但是這樣仍然不好處理上面提到的問題,考慮讓線段橫著走,第一個撞著的長方形就是答案。為了實現這個目標,就每個長方形向下移動一段(移動的距離與它和它距起點的距離有關)
比如可能修改後就成了這樣。然後就可以從後向前區間覆蓋來預處理每個路口剛等完紅燈然後到終點的耗時。
Code
1 // 2018-2-13 Test 2 #include <iostream> 3 #include <cstdio> 4 #include <cmath> 5 #include <ctime> 6 #include <map> 7 #ifndef WIN32 8 #define Auto "%lld" 9 #else 10 #define Auto "%I64d" 11 #endif 12 using namespace std; 13 typedef bool boolean;14 #define ll long long 15 #define iter map<int, int>::iterator 16 17 int n, g, r, s; 18 int q; 19 ll *len; 20 ll *dis; 21 map<int, int> mp; 22 23 inline void init() { 24 scanf("%d%d%d", &n, &g, &r); 25 s = g + r; 26 len = new ll[(n + 2)]; 27 dis = newll[(n + 2)]; 28 for (int i = 0; i <= n; i++) 29 scanf(Auto, len + i); 30 } 31 32 inline void cover(int l, int r, int x) { 33 iter il = mp.lower_bound(l), ir = mp.upper_bound(r); 34 int temp = -1; 35 if (ir == mp.end() || ir->first > r + 1) { 36 temp = (--ir)->second; 37 ir++; 38 } 39 mp.erase(il, ir); 40 mp[l] = x; 41 if (~temp) 42 mp[r + 1] = temp; 43 // cerr << l << " " << r << endl; 44 } 45 46 inline void solve() { 47 mp[0] = n + 1; 48 dis[n + 1] = 0; 49 for (int i = 1; i <= n; i++) 50 len[i] += len[i - 1]; 51 iter it; 52 for (int i = n; i; i--) { 53 int r = -len[i - 1] % s, l = r + g; 54 if (r < 0) r += s; 55 if (l < 0) l += s; 56 it = mp.upper_bound(r); 57 int j = (--it)->second; 58 int rest = (r + len[j - 1]) % s; 59 int wait = (j != n + 1 && rest >= g) ? (s - rest) : (0); 60 dis[i] = len[j - 1] - len[i - 1] + wait + dis[j]; 61 r = (r) ? (r - 1) : (s - 1); 62 if (l <= r) 63 cover(l, r, i); 64 else 65 cover(0, r, i), cover(l, s - 1, i); 66 // cerr << dis[i] << endl; 67 } 68 ll t; 69 scanf("%d", &q); 70 while (q--) { 71 scanf(Auto, &t); 72 it = mp.upper_bound(t % s); 73 int j = (--it)->second; 74 int r = (t + len[j - 1]) % s; 75 int wait = (j != n + 1 && r >= g) ? (s - r) : (0); 76 printf(Auto"\n", t + wait + len[j - 1] + dis[j]); 77 } 78 } 79 80 int main() { 81 freopen("brt.in", "r", stdin); 82 freopen("brt.out", "w", stdout); 83 init(); 84 solve(); 85 return 0; 86 }
題目大意
給定$A$數組,每次可以選出$A$數組中沒有選過的一個無序對$(A_{i},A_{j})$,然後得到的分數是$A_{i} xor A_{j}$,問選取$m$次得到的最大的分數和模$10^{9} + 7$
想罵一句這道題,100%的數據不給$m$的範圍,我一直以為$n, m$同階,沒見過這樣坑題面的。。。
顯然需要把每個數放入Trie樹中。然後二分一下得到第$m$大的值。
然後考慮在Trie上搞點小計數來得到前$k$大和指定數的異或值的和。考慮每個節點記錄一下它的子樹中包含的數,每一位上的1的個數。
然後就可以做了。
真心懷疑這道題有毒,考試時用空間復雜度O(31(n + m))的算法,然後MLE。考完後改題,死活因為常數問題TLE。。為什麽這道題這麽不友好
Code
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 #include <queue> 5 using namespace std; 6 typedef bool boolean; 7 8 const int M = 1e9 + 7; 9 10 typedef class TrieNode { 11 public: 12 int cnt; 13 int cnts[32]; 14 TrieNode *ch[2]; 15 16 TrieNode() { } 17 TrieNode(int cnt, TrieNode* ch1, TrieNode* ch2):cnt(cnt) { 18 ch[0] = ch1, ch[1] = ch2; 19 memset(cnts, 0, sizeof(cnts)); 20 } 21 }TrieNode; 22 23 #define Limit 1700000 24 25 TrieNode pool[Limit]; 26 TrieNode* top = pool; 27 28 TrieNode* newnode() { 29 if (top == pool + Limit) 30 return new TrieNode(0, NULL, NULL); 31 top->cnt = 0, top->ch[0] = top->ch[1] = NULL; 32 memset(top->cnts, 0, sizeof(top->cnts)); 33 return top++; 34 } 35 36 TrieNode* newnode(TrieNode* old) { 37 TrieNode* rt = newnode(); 38 if (old == NULL) return rt; 39 *rt = *old; 40 return rt; 41 } 42 43 typedef class Trie { 44 public: 45 TrieNode* rt; 46 47 Trie():rt(NULL) { } 48 49 TrieNode* modify(TrieNode* p, int x, int temp, int cnt) { 50 TrieNode* np = newnode(p); 51 int d = ((x & temp) ? (1) : (0)); 52 np->cnt += cnt; 53 if (!temp) return np; 54 for (int i = 0, j = 1; i <= 30; i++, j <<= 1) 55 np->cnts[i] += ((x & j) ? (1) : (0)); 56 np->ch[d] = modify(np->ch[d], x, temp >> 1, cnt); 57 return np; 58 } 59 60 void modify(int x, int cnt) { 61 rt = modify(rt, x, 1 << 30, cnt); 62 } 63 64 int rank(int x, int a) { 65 TrieNode *p = rt; 66 int ret = 0, temp = 1 << 30; 67 while (temp && p) { 68 int dx = ((x & temp) ? (1) : (0)), da = ((a & temp) ? (1) : (0)); 69 if (dx == 0) 70 ret += ((p->ch[da ^ 1]) ? (p->ch[da ^ 1]->cnt) : (0)), p = p->ch[da]; 71 else 72 p = p->ch[da ^ 1]; 73 temp >>= 1; 74 } 75 return ret; 76 } 77 78 int ksums(int k, int a) { 79 TrieNode* p = rt; 80 int ret = 0, temp = 1 << 30; 81 while (temp && p) { 82 int da = ((a & temp) ? (1) : (0)); 83 int ls = (p->ch[da ^ 1]) ? (p->ch[da ^ 1]->cnt) : (0); 84 if (k >= ls) { 85 TrieNode* l = p->ch[da ^ 1]; 86 if (l) 87 for (int i = 0, j = 1, dx; i <= 30; i++, j <<= 1) { 88 dx = ((a & j) ? (l->cnt - l->cnts[i]) : (l->cnts[i])); 89 ret = (ret + (dx * 1ll * j)) % M; 90 } 91 k -= ls; 92 p = p->ch[da]; 93 } else 94 p = p->ch[da ^ 1]; 95 temp >>= 1; 96 } 97 return ret; 98 } 99 }Trie; 100 101 int n, m; 102 int* ar; 103 Trie* ts; 104 105 inline void init() { 106 scanf("%d%d", &n, &m); 107 ar = new int[(n + 1)]; 108 ts = new Trie[(n + 1)]; 109 for (int i = 1; i <= n; i++) 110 scanf("%d", ar + i); 111 } 112 113 inline int check(int mid) { 114 long long rt = 0; 115 for (int i = 2; i <= n && rt < m; i++) 116 rt += ts[i].rank(mid, ar[i]); 117 return rt; 118 } 119 120 int res = 0; 121 inline void solve() { 122 for (int i = 2; i <= n; i++) { 123 ts[i].rt = ts[i - 1].rt; 124 ts[i].modify(ar[i - 1], 1); 125 } 126 127 int l = 0, r = (signed) (~0u >> 1); 128 while (l <= r) { 129 int mid = l + ((r - l) >> 1); 130 if (check(mid) < m) 131 r = mid - 1; 132 else 133 l = mid + 1; 134 } 135 136 int sk = 0, k; 137 for (int i = 2; i <= n; i++) { 138 k = ts[i].rank(r + 1, ar[i]); 139 res = (res + ts[i].ksums(k, ar[i])) % M; 140 sk += k; 141 } 142 143 res = (res + (m - sk) * 1ll * (r + 1)) % M; 144 145 printf("%d\n", res); 146 } 147 148 int main() { 149 freopen("xor.in", "r", stdin); 150 freopen("xor.out", "w", stdout); 151 init(); 152 solve(); 153 return 0; 154 }
題目大意
請維護一個數據結構,使得它能支持:
- 區間加上$k$
- 區間詢問從1開始的前綴和的最大值
記得去年省選後,自己yy出了這麽一道題,然而並不會做,就跑去問學長,學長告訴我分塊加斜率等等,然而我並沒有懂,覺得這麽"獨流"的題應該沒人考吧。現在覺得臉好疼啊。
但是我覺得最毒的是,我寫的純暴力在氧氣優化下拿了80分.全班開始都以為我是正解寫掛了。
考慮區間加上$k$對前綴和的影響,畫個圖比較直觀:
其中一段加的是等差數列,後一段就是普通的區間加法。
若將每個位置看成一個點,橫坐標為下標,縱坐標為前綴和,那麽對詢問區間維護一個上凸殼,答案就是斜率為0的直線和凸殼的切點。
對於區間加上一個公差為k的等差數列,相當於任意兩點之間斜率增加$k$。修改後凸殼的點集顯然不會改變。另外標記顯然可加。
但是難以快速合並兩個凸殼,為了避免合並凸殼,所以采用分塊。
每個塊維護一個凸殼,斜率標記,區間加法標記。
考慮修改操作,區間兩端暴力重構塊,中間的打tag,後邊把tag也打上。
考慮查詢操作,區間兩端暴力找,中間在凸殼上二分(只是註意一下tag的存在),最後把答案一起取個max。
中間取值的時候不要忘記tag的存在。
(由於這道題和bzoj 2388除了題目描述不一樣,我就直接貼bzoj 2388的代碼了)
Code
1 /** 2 * bzoj 3 * Problem#2388 4 * Accepted 5 * Time: 39696ms 6 * Memory: 3520k 7 */ 8 #include <bits/stdc++.h> 9 #ifndef WIN32 10 #define Auto "%lld" 11 #else 12 #define Auto "%I64d" 13 #endif 14 using namespace std; 15 typedef bool boolean; 16 #define ll long long 17 18 const int cs = 350; 19 const double eps = 1e-8; 20 21 typedef class Chunk { 22 public: 23 int s, ed; 24 ll lazyk; 25 ll lazys; 26 ll ar[cs]; 27 int con[cs]; 28 29 Chunk():s(0), ed(-1), lazyk(0), lazys(0) { } 30 31 double slope(int x1, int x2) { 32 return (ar[x2] - ar[x1]) * 1.0 / (x2 - x1); 33 } 34 35 void update() { 36 if (!lazyk && !lazys) return; 37 for (int i = 0; i < s; i++) 38 ar[i] += lazyk * (i + 1); 39 for (int i = 0; i < s; i++) 40 ar[i] += lazys; 41 lazyk = lazys = 0; 42 } 43 44 void rebuild() { 45 ed = -1; 46 for (int i = 0; i < s; i++) { 47 while (ed >= 1 && slope(con[ed - 1], con[ed]) + eps < slope(con[ed], i)) 48 ed--; 49 con[++ed] = i; 50 } 51 } 52 53 int query() { 54 int l = 0, r = ed; 55 while (l <= r) { 56 int mid = (l + r) >> 1; 57 if (!mid || slope(con[mid - 1], con[mid]) + lazyk > eps) 58 l = mid + 1; 59 else 60 r = mid - 1; 61 } 62 return con[l - 1]; 63 } 64 65 ll getRealValue(int p) { 66 return ar[p] + (p + 1) * lazyk + lazys; 67 } 68 }Chunk; 69 70 int n, m; 71 ll *ar; 72 Chunk ch[350]; 73 74 inline void init() { 75 scanf("%d", &n); 76 ar = new ll[(n + 1)]; 77 Chunk* p = ch; 78 for (int i = 0; i < n; i++) 79 scanf(Auto, ar + i); 80 for (int i = 1; i < n; i++) 81 ar[i] += ar[i - 1]; 82 for (int i = 0; i < n; i++) { 83 p->ar[p->s++] = ar[i]; 84 if (p->s == cs) 85 p->rebuild(), p++; 86 } 87 } 88 89 inline void bmodify(Chunk* p, int l, int r, ll k, ll s) { 90 p->update(); 91 for (int i = 0; i <= r - l; i++) 92 p->ar[l + i] += s + k * (i + 1); 93 p->rebuild(); 94 } 95 96 inline ll bquery(Chunk* p, int l, int r) { 97 p->update(); 98 ll rt = p->ar[l]; 99 for (int i = l + 1; i <= r; i++) 100 if (rt < p->ar[i]) 101 rt = p->ar[i]; 102 return rt; 103 } 104 105 inline void solve() { 106 int opt, l, r; 107 ll k, base; 108 scanf("%d", &m); 109 while (m--) { 110 scanf("%d%d%d", &opt, &l, &r); 111 l--, r--; 112 if (!opt) { 113 scanf(Auto, &k); 114 base = 0; 115 for (int i = l, ni; i <= r; i = ni) { 116 ni = (i / cs + 1) * cs; 117 Chunk* p = ch + (i / cs); 118 if ((i % cs) || ni > r) 119 bmodify(p, i % cs, (ni > r) ? (r % cs) : (cs - 1), k, base); 120 else 121 p->lazyk += k, p->lazys += base; 122 base += (ni - i) * k; 123 } 124 base = k * (r - l + 1); 125 for (int i = r + 1, ni; i < n; i = ni) { 126 ni = (i / cs + 1) * cs; 127 Chunk* p = ch + (i / cs); 128 if ((i % cs) || ni >= n) 129 bmodify(p, i % cs, (ni >= n) ? ((n - 1) % cs) : (cs - 1), 0, base); 130 else 131 p->lazys += base; 132 } 133 } else { 134 ll res = 1ll << 63, cmp; 135 for (int i = l, ni; i <= r; i = ni) { 136 ni = (i / cs + 1) * cs; 137 Chunk *p = ch + (i / cs); 138 if ((i % cs) || ni > r) 139 cmp = bquery(p, i % cs, (ni > r) ? (r % cs) : (cs - 1)); 140 else 141 cmp = p->getRealValue(p->query()); 142 if (cmp > res) 143 res = cmp; 144 } 145 printf(Auto"\n", res); 146 } 147 } 148 } 149 150 int main() { 151 init(); 152 solve(); 153 return 0; 154 }
2018.2.12 省選模擬賽