## Spring Practices 03 心得&題解 [<<回主頁](https://hackmd.io/@r1cky/Sk2hKzga6) ### 前言 這次難了好多... D 好難賽裡沒寫出來,沒有人賽中破台。 ### pA 排序 Pair 們,依照起點來排序,如果能排我們就暫時放入那個區間,然後記得他現在的尾端,而這裡因為我們已經把他依起點排序,所以在沒有這個活動的情況下後面的一定是都排得進去的。 然後如果發現結束時間比目前暫時放進去的早的話,那就把他替換掉。 bottle-neck 是排序,時間複雜度 $O(nlgn)$。 Code : ```cpp= // Author : rickytsung // Problem : // Date : #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #include <bits/stdc++.h> #define i64 long long #define i128 __int128 #define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define pii pair<int, int> #define f first #define s second #define pb(x) push_back(x) using namespace std; mt19937 rng(114514); const int mod1 = 998244353; int rnd(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } i64 gcd(i64 a,i64 b){ if (a%b == 0) return(b); else return(gcd(b, a%b)); } i64 lcm(i64 a, i64 b){ return a/gcd(a, b) * b; } bool cmp(pii f, pii s){ return f.f < s.f; } int main() { IOS; int n; int x, y; cin >> n; vector<pii> S(n); for(int i = 0; i < n;i++){ cin >> x >> y; S[i] = make_pair(x,y); } sort(S.begin(), S.end(), cmp); int last = -1, ans = 0 ; for(int i = 0; i < n; i++){ if(last > S[i].f){ last = min(last, S[i].s); } else{ ans ++; last = S[i].s; } } cout << ans << '\n'; } ``` ### pB 紀錄說 「過小的」最大值、 「過大的」 最小值,以及正確答案的值,看關係合不合理就可以了。 時間複雜度 O(line) Code : ```cpp= // Author : rickytsung // Problem : // Date : #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #include <bits/stdc++.h> #define i64 long long #define i128 __int128 #define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define pii pair<int, int> #define f first #define s second #define pb(x) push_back(x) using namespace std; mt19937 rng(114514); const int mod1 = 998244353; int rnd(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } i64 gcd(i64 a,i64 b){ if (a%b == 0) return(b); else return(gcd(b, a%b)); } i64 lcm(i64 a, i64 b){ return a/gcd(a, b) * b; } bool cmp(pii f, pii s){ return f.f < s.f; } int main() { IOS; string nn; int mux = 0, mun = 11, right = -1, n; string s, s2; while(1){ cin >> nn; n = stoi(nn); if(n == 0) break; cin >> s >> s2; if(s2 == "on"){ if(mux < n && n < mun){ cout << "Stan may be honest\n"; } else{ cout << "Stan is dishonest\n"; } mux = 0; mun = 11; right = -1; } if(s2 == "low"){ mux = max(mux, n); } if(s2 == "high"){ mun = min(mun, n); } } } ``` ### pC 模擬一個 stack 就可以了,拿到左括號是加入就 push 進去,拿到右就 pop,然後看一不一樣。 時間複雜度 $O(N)$。 我沒發現有可能有輸入是整個空的,用```cin string```會吃不到,吃一堆 WA。 然後我吃毒用 ```gets()``` 準備被紀老師當掉,後來才知道有 ```cin.ignore()``` ,請大家不要學我這個小丑,請用別的來輸入。 Code : ```cpp= // Author : rickytsung // Problem : // Date : #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #include <bits/stdc++.h> #define i64 long long #define i128 __int128 #define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define pii pair<int, int> #define f first #define s second #define pb(x) push_back(x) using namespace std; mt19937 rng(114514); const int mod1 = 998244353; int rnd(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } i64 gcd(i64 a,i64 b){ if (a%b == 0) return(b); else return(gcd(b, a%b)); } i64 lcm(i64 a, i64 b){ return a/gcd(a, b) * b; } bool cmp(pii f, pii s){ return f.f < s.f; } int main() { //IOS; char s[1024]; int t, n, ptr = -1, maxn = 1024; int st[maxn]; gets(s); t = stoi(s); while(t--){ gets(s); ptr = -1; for(int i = 0; i < maxn; i++){ st[i] = 0; } int ans = 1; for(int i = 0 ; s[i] != '\0'; i++){ if(s[i] == '('){ st[++ptr] = 1; } if(s[i] == '['){ st[++ptr] = 2; } if(s[i] == ')'){ if(ptr <= -1) { ans = 0; break; } if(st[ptr] != 1){ ans = 0; break; } ptr --; } if(s[i] == ']'){ if(ptr <= -1) { ans = 0; break; } if(st[ptr] != 2){ ans = 0; break; } ptr --; } // cout << ptr << '\n'; } if(ans && (ptr == -1)){ cout << "Yes\n"; } else{ cout << "No\n"; } } } ``` ### pD 可以發現這題的輸入長得非常畸形。 其實不用想太多,這不是數學家題,題目只是找理由說讓你覺得這 $H$ 生成方式很合理 (?,阿為什麼他不好好輸入只是因為輸入量有點大,用 cin 不用 fread 之類的可能會 TLE。 像下面 [這題](https://atcoder.jp/contests/abc344/tasks/abc344_g) 就講得很明白,跟你說 $Q$ 大的感人所以請你自己生。 ![image](https://hackmd.io/_uploads/ByxeroHRa.png) 那反正就是照題目說的產生 $G, H$,然後對所有滿足 $A \times B$ 的 $H$ 的子陣列做事。 如果直接照題目的做,對 $AB$ 個格子取最小,時間複雜度是 $O(NMAB)$,看似會 TLE,實際真的會 TLE,所以要想更好的方法。 那這個二維的我覺得很麻煩,所以我想先做一維的,那問題會變這樣 : > 給你一個長度 $n$ 的非負整數序列 $a$ 和 $k$,求 $MIN(a_1, a_2, ..., a_k)+MIN(a_2, a_3, ..., a_{k+1})+...+MIN(a_{n-k+1}, a_{n-k+2}, ..., a_n)$ 我們試著找比 $O(n^2)$ 更好的方法 : 我們維護一個 deque ,這個 deque 裡面裝 pair $(x,y)$,$x$ 代表放進來的時間、$y$ 代表他的值,我們希望他能有個單調 (monotone) 性質, 從左到右 $x$ 能越來越大、而 $y$ 也越來越大。 考慮以下測資 : $(n, k) = (5,2)$ ![image](https://hackmd.io/_uploads/Hy-1tiBA6.png) 一開始我們希望我們先把前兩個值放進去,從第三個開始要注意左邊的值要拿掉 那麼,我們的 deque 會做下列事情 : ![image](https://hackmd.io/_uploads/H1brFjSCT.png) 然後因為我們維護了最小的值出現在左邊,所以最左邊的 $1$ 是答案! 那我們接著放入第三個元素 : ![image](https://hackmd.io/_uploads/Hki0FirAa.png) 接著我們一樣從左邊拿答案,但是注意最左邊的已經超出範圍了! (編號 1 現在在我們要的範圍外面了),我們把它丟掉,然後取下一個,也就是 $2$。 ![image](https://hackmd.io/_uploads/HkPZqiHA6.png) 下一個是放入第四個元素,一樣的過程 : ![image](https://hackmd.io/_uploads/BJwrcoBRp.png) 最後放入第五個元素,但因為它滿小的,所以不太一樣喔 ! 我們不斷檢查右端的元素是否比它大,是的話就丟掉,直到找到有人比他小又或著 deque 變空的。 ![image](https://hackmd.io/_uploads/rku0qiBR6.png) 維護 deque 的總時間複雜度就是 $O(n)$。 -------------------------- 回到原題,二維的,我們想一個小性質 : 這兩個是不是根本一樣的(各顏色代表該區塊最小值) ? ![image](https://hackmd.io/_uploads/Bkly3jH06.png) 確實其實是一樣的。 那我們可以用前面的維護 $n$ 個這樣的長條東西,用前面一維的方法應該就可以 $O(m)$ 維護一個長條從左掃到最右邊,每一次的值了吧 ! 維護了這個值後,每個長條化成一個值而已,那這個問題想成另個方向就又變成另一個相同的一維的問題。 最後我們可以 $O(nm)$ 解決。 Code : ```cpp= // Author : rickytsung // Problem : // Date : #pragma GCC optimize("O3,unroll-loops") #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt") #include <bits/stdc++.h> #define i64 long long #define i128 __int128 #define IOS ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define pii pair<int, int> #define f first #define s second #define pb(x) push_back(x) using namespace std; mt19937 rng(114514); const int mod1 = 998244353; int rnd(int l, int r) { return uniform_int_distribution<int>(l, r)(rng); } i64 gcd(i64 a,i64 b){ if (a%b == 0) return(b); else return(gcd(b, a%b)); } i64 lcm(i64 a, i64 b){ return a/gcd(a, b) * b; } bool cmp(pii f, pii s){ return f.f < s.f; } int add(int i, int l, int r, int v, pii* q, int* pl, int* pr){ while(v <= q[pr[i]].s && pl[i] <= pr[i]) pr[i]--; ++pr[i]; q[pr[i]] = make_pair(r, v); while(l > q[pl[i]].f) pl[i]++; return q[pl[i]].s; } int main() { IOS; int n, m, a, b; cin >> n >> m >> a >> b; int nm = (n+5) * (m+5), sz = n+1, qsz = max(n,m) + 5; i64 g[nm]; pii q[sz][qsz]; // q -> (idx, v) (monotone deque) int l[sz], r[sz]; // 0 -> whole 1~n -> a row int com[n+5]; int h[n+5][m+5]; i64 x, y, z, ans = 0; cin >> g[0] >> x >> y >> z; for(int i = 1; i < nm; i++){ g[i] = (g[i-1] * x + y) % z; // cal g } for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++){ h[i][j] = g[(i-1) * m + (j-1)]; // find h } } for(int i = 0; i < sz; i++){ l[i] = 0; r[i] = -1;// init } for(int j = 1; j < b; j++){ for(int i = 1; i <= n; i++){ add(i, -1, j, h[i][j], q[i], l, r); } } for(int j = b; j <= m; j++){ for(int i = 1; i <= n; i++){ com[i] = add(i, j - b + 1, j, h[i][j], q[i], l, r); } l[0] = 0; r[0] = -1;// init for(int i = 1; i < a; i++){ add(0, -1, i, com[i], q[0], l, r); } for(int i = a; i <= n; i++){ ans += add(0, i - a + 1, i, com[i], q[0], l, r); } } cout << ans << '\n'; } ``` ### pE 實作用 map (dict) 把字串轉成 id,就變成一個二維陣列排序題。 然後 Python 比較好做就用 Python 了。 時間複雜度 $O(NlgN+Q)$ Code (Python) : ``` python= # Author : rickytsung n = int(input()) hsah = 0 dicc = {} arr = [] for i in range(n): s = input().split(" ") if(dicc.get(s[0]) is None): dicc[s[0]] = hsah hsah += 1 arr.append([]) arr[dicc[s[0]]].append(int(s[1])) for i in range(len(arr)): (arr[i]).sort() q = int(input()) for i in range(q): s = input().split(" ") print(arr[dicc.get(s[0])][int(s[1])-1]) ```