# 114KSPGC test2 題解 ###### tags: `題解` <!-- 註:題號未定 --> ## pA The Tower **Idea:** Gana31415 **Prepration:** Gana31415 :::spoiler **Tags** `dp` ::: <a> </a> 這是一個遞迴問題。假設你爬到了第11層,你的前一步有可能是從第10層上升1層、或是從第8層上升3層、或是從第1層上升10層,假設$a_n$表示爬到第$n$層的方法數,那麼以上例子可以寫成 \begin{aligned} a_{11}=a_{10}+a_8+a_1 \end{aligned} 延伸到第$n$層就是 \begin{aligned} a_n=a_{n-1}+a_{n-3}+a_{n-10} \end{aligned} 再來用dp就能夠解了 ::: spoiler 官解 (Gana31415) ```cpp= #include<bits/stdc++.h> using namespace std; typedef long long ll; ll dp[105] = {0}; void Dp(){ dp[1] = 1, dp[3] = 1 , dp[10] = 1; for(int i = 2 ; i <= 100 ; i++){ int a = i-1 , b = i-3 , c = i-10; b = max(0 , b) , c = max(0 , c); dp[i] += dp[a] + dp[b] + dp[c]; } } int main(){ ll n; cin >> n; Dp(); cout << dp[n] <<"\n"; return 0; } ``` ::: ::: spoiler 另解 (niter) 不同方向的dp ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<b;i++) using namespace std; int main(){ long long a[200] = {0}; a[0] = 1; loop(i,0,101){ a[i+1] += a[i]; a[i+3] += a[i]; a[i+10] += a[i]; } int n; cin >> n; cout << a[n] << "\n"; } ``` ::: ## pB Bomb!! **Idea:** Gana31415 **Prepration:** Gana31415 :::spoiler **Tags** `dp` ::: <a> </a> 利用dp可以解答,最簡單的方式是用二維$dp[i][j]$來記錄在第$i$個位置時還要傳$j$次才爆炸。當你接到炸彈時的前一刻有可能是由你的右邊或是左邊傳過來的,因此有轉移式: \begin{aligned} dp[i][j]=dp[i-1][j+1]+dp[i+1][j+1] \end{aligned} 注意一下編號就能過了。當然可以做空間壓縮,會更漂亮。 ::: spoiler 官解 (Gana31415) ```cpp= #include<bits/stdc++.h> using namespace std; typedef long long ll; int dp[105][105] = {0}; int main(){ int n,m; cin >> n >> m; dp[0][m] = 1; for(int j = m ; j > 0 ; j--) for(int i = 0 ; i < n ; i++){ int a = i-1, b = i+1; if(a == -1) a = n-1; if(b == n) b = 0; dp[i][j-1] = dp[a][j] + dp[b][j]; } cout << dp[0][0] << '\n'; return 0; } ``` ::: ::: spoiler 另解 (niter) ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<b;i++) using namespace std; int main(){ int n, m; cin >> n >> m; vector<long long> a(n, 0); a[0] = 1; loop(i,0,m){ vector<long long> b(n, 0); loop(j,0,n){ b[(j+1+n) % n] += a[j]; b[(j-1+n) % n] += a[j]; } a = b; } cout << a[0] << "\n"; } ``` ::: ## pC Sum it all **Idea:** niter **Prepration:** niter :::spoiler **Tags** `greedy, sorting` ::: <a> </a> 基本上,在不證明這個性質的情況就AC這題是比較好的做題過程。 因為通常賽中不會有時間慢慢想證明方法,大部分都是「覺得好像對就寫下去了」。 當然在寫之前要先試幾組看會不會有反例啦。 以下是完整解法: 因為先把$a$陣列排序不影響最終的YES/NO,所以為了方便,我們先把$a$陣列由大到小排序。($b$不動) 你可以想成是我們把原本的問題改寫成了與它等價的問題。 接下來,我們要證明:再把$b$陣列由小到大排序後,能使$\sum\limits_{i=1}^n (a_i)^{b_i}$的值達到最小值。 在$a$陣列由大到小排序之後($b$陣列還沒排序),假設有一組$(i, j)$,使得$\left\{\begin{aligned}{a_i\geq a_j\geq 1} \\{b_i\geq b_j\geq 1}\end{aligned}\right.$, 可以證明$b_i,b_j$交換後,$\sum\limits_{i=1}^n (a_i)^{b_i}$的值會變得更小(或是至少不變)。 > 證明過程: > > 因為$\left\{\begin{aligned}{a_i\geq a_j\geq 1} \\{b_i\geq b_j\geq 1}\end{aligned}\right.$, > > $\Rightarrow\left\{\begin{array}{l}{(a_i)^{b_j}\geq (a_j)^{b_j}\geq 1} \\{(a_i)^{b_i-b_j}-1\geq (a_j)^{b_i-b_j}-1\geq 0}\end{array}\right.$, > > $\Rightarrow\Large(\normalsize(a_i)^{b_j}\Large)(\normalsize(a_i)^{b_i-b_j}-1\Large) \large \geq \Large(\normalsize(a_j)^{b_j}\Large)(\normalsize(a_j)^{b_i-b_j}-1\Large)$, > > $\Rightarrow(a_i)^{b_i}-(a_i)^{b_j}\geq (a_j)^{b_i}-(a_j)^{b_j}$, > > 所以$(a_i)^{b_i}+(a_j)^{b_j}\geq (a_j)^{b_i}+(a_i)^{b_j}$,故得證。 如果我們不做無意義的交換($b_i=b_j$),那麼我們必定能在有限次內, 使這些交換最後讓$b$陣列由小到大排序完成, 而此時透過反證法可以得知$\sum\limits_{i=1}^n (a_i)^{b_i}$ 的值不可能再變小了, 所以這時只要在檢查它是否有小於$x$,就可以輸出答案了! 實作細節就是不要溢位,乘到超過${10}^9$要記得break。 可以不使用快速冪來實作,因為$1$的幾次方都是$1$,可以特判,而$2$最多乘到$30$次方就會超過${10}^9$了, $3$就更小了,所以用for迴圈算次方就只是差一個常數的時間而已。 ::: spoiler 官解 (niter) ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i = a; i < b;i++) using namespace std; int main(){ int n; long long x; cin >> n >> x; long long a[n + 5], b[n + 5]; loop(i,0,n) cin >> a[i]; loop(i,0,n) cin >> b[i]; sort(a, a + n, less<int>()); sort(b, b + n, greater<int>()); long long ret = 0, tmp; bool ans = true; loop(i,0,n){ if(a[i] == 1){ ret++; } else{ tmp = 1; loop(j,0,b[i]){ tmp *= a[i]; if(tmp > x){ ans = false; break; } } ret += tmp; } if(ret > x){ ans = false; break; } } if(!ans){ cout <<"NO\n"; } else{ cout << "YES\n"; loop(i,0,n) cout << a[i] << " "; cout << '\n'; loop(i,0,n) cout << b[i] << " "; cout << '\n'; } return 0; } ``` ::: ## pD Take some numbers! **Idea:** niter **Prepration:** niter :::spoiler **Tags** `dp` ::: <a> </a> 經過觀察可以發現這題其實不是數學題,而是湊不湊的到的無限背包問題。 我們定義$dp_i$代表數字$i$湊不湊得到,湊得到就是$1$,否則就是$0$,那麼可以得知以下結論: 如果$dp_i=1$,那麼$dp_{i+a_j}=1(1\leq j\leq n)$,而且$dp_0=1$。 所以我們只要使用for迴圈,由小到大跑過所有的$i$,對每個$i$再跑過所有的$j$即可。 複雜度$O(nC)$,$C=10^6$為詢問範圍。 ::: spoiler 官解(niter) ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<b;i++) using namespace std; const int _1e6_ = (int)(1e6); int main(){ int n, q; cin >> n >> q; int a[n+5]; loop(i,0,n){ cin >> a[i]; } bool can[_1e6_ + 100] = {0}; can[0] = 1; loop(i,0,_1e6_+1){ if(!can[i]) continue; loop(j,0,n){ if(i + a[j] <= _1e6_){ can[i + a[j]] = true; } } } int qry; loop(i,0,q){ cin >> qry; cout << can[qry] << ' '; } cout << '\n'; return 0; } ``` ::: ## pE KSRPGC **Idea:** niter **Prepration:** 2006lennon :::spoiler **Tags** `data structures(STL)` ::: <a> </a> 由於一個技能可以使用多次,所以每個$(a,b)$都只需要被算一次,那麼可以使用set不能有重複元素的特性來維護(無法插入已經存在的元素),特別注意如果他沒有移動的話,他並不需要發動技能,也就是$(0,0)$的情況不應被列入技能中 ::: spoiler 官解 (2006lennon) ```cpp= #include <bits/stdc++.h> typedef long long ll; #define pii pair <ll,ll> #define f first #define s second using namespace std; int main(){ ios::sync_with_stdio(0); cin.tie(0); ll s,t; cin>>s>>t; set <pii> st; ll n=t-s+1; vector <ll> x(n),y(n); for(ll i=0;i<n;i++){ cin>>x[i]>>y[i]; } for(int i=1;i<n;i++){ st.insert({x[i]-x[i-1],y[i]-y[i-1]}); } st.erase({0,0}); cout<<st.size()<<"\n"; return 0; } ``` ::: ## pF Listen to the class **Idea:** sean9575 **Prepration:** sean9575 :::spoiler **Tags** `data structures` ::: <a> </a> 待補 ::: spoiler 官解 (sean9575) ``` ``` ::: ## pG Shopping Street and a Tour Guide **Idea:** sean9575 **Prepration:** niter :::spoiler **Tags** `constructive algorithms, 梗題` ::: <a> </a> 鄭勝宇原本說要拿這題來考線段樹的,後來被發現這題其實超水,所以就被我拿來平衡難度了。 在他的原題中,線段(把行程想成是線段)是沒有權重的,而且是個裸題。 作法: 取一個左邊界是$k$的線段(這樣它就贏所有在它左邊的店了),再取一個右邊界是$k$的線段(使它贏過所有在它右邊的店), 就能使編號$k$的店,達到來客數最多,而且沒有其他店跟他一樣多。 也有可能一個線段同時滿足這兩個條件,就是$[k, k]$,這時只要取這個線段就可以了。 ::: spoiler 官解 (niter) `segment_l[k]`, `segment_r[k]`來分別代表有幾個線段左、右邊界是k。 ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<(b);i++) using namespace std; const int maxN = (int)(1e6) + 5; struct route{ int l = 0, r = 0, x = 0; }; route arr_route[maxN]; int segment_l[maxN], segment_r[maxN]; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n, q; cin >> n >> q; int qry, a, k; loop(i,0,q){ cin >> qry; if(qry == 1){ cin >> a; cin >> arr_route[a].l; cin >> arr_route[a].r; cin >> arr_route[a].x; segment_l[arr_route[a].l]++; segment_r[arr_route[a].r]++; } else if(qry == 2){ cin >> k; segment_l[arr_route[k].l]--; segment_r[arr_route[k].r]--; arr_route[k] = {0, 0, 0}; } else{ // qry == 3 cin >> k; if(segment_l[k] > 0 && segment_r[k] > 0){ cout << "1 "; } else{ cout << "0 "; } } } cout << '\n'; } ``` ::: ## pH Hotel **Idea:** 2006lennon **Prepration:** 2006lennon :::spoiler **Tags** `sorting, greedy, data structures, implementation` ::: <a> </a> 對於區間$[l,r]$來說,對左界$l$排序,如果在左界$l$相同的情況下,優先讓區間較小的選擇編號最小的房間 但如果是$[1,3],[1,3],[2,2]$的情況,第一人選1,第二人選2,第三個選不了了,但其實有方法可以將所有人都入住 我們可以維護一個最小值$now$,表示最小可填入的房間,如果有$r$小於$now$,那麼將無法再繼續入住,也就沒有符合的答案了 因此我們可以使用priority_queue來維護$r$的最小值,如果最小的$r$小於$now$,說明已沒有可入住的房間,反之我們可以刪除這個$r$,並將$now$+1 ::: spoiler 官解 (2006lennon) ```cpp= #include <bits/stdc++.h> using namespace std; #define pii pair <int,int> #define f first #define s second int main(){ cin.tie(0); ios::sync_with_stdio(0); int n,m,now=-1; cin>>n>>m; bool ans=true; vector <pii> v(n); for(int i=0;i<n;i++) cin>>v[i].f>>v[i].s; sort(v.begin(),v.end()); v.push_back({m+5,m+5}); priority_queue <int,vector<int>,greater<int> > q; for(int i=0;i<=n;i++){ while(!q.empty() && now<v[i].f){ if(q.top()<now){ ans=false; break; } if(!ans) break; q.pop(); now++; } q.push(v[i].s); now=v[i].f; } if(ans) cout<<"Yes\n"; else cout<<"No\n"; return 0; } ``` ::: ## pI NPC amount reporting **Idea:** sean9575 **Prepration:** sean9575 :::spoiler **Tags** `data structures` ::: <a> </a> 待補 ::: spoiler 官解 (sean9575) ``` ``` ::: ## pJ Nearest Pair in Array **Idea:** niter **Prepration:** niter :::spoiler **Tags** `Sorting and Searching, data structures` ::: <a> </a> 原本的式子:$c_{i, j}=|a_i-a_j|-|i-j|$ 因為$c_{i, j} = c_{j, i}$,所以我們可以不失一般性假設$i<j$ 這樣我們就可以把其中一個絕對值展開了。 $\Rightarrow c_{i, j}=|a_i-a_j|-(j-i)$ $\Rightarrow c_{i, j}=|a_i-a_j|+i-j$ 這樣還不夠,我們可以透過判case來把另外一個絕對值展開。 $\Rightarrow c_{i, j}=\left\{\begin{array}{l}{(a_i+i)-(a_j+j)}\quad\quad\quad{(a_i\geq a_j)} \\{(a_j-j)-(a_i-i)}\quad\quad\quad{(a_i\leq a_j)}\end{array}\right.$ 在$a_i\geq a_j$的case中,我們可以用BIT紀錄每個$a_i$的值所對應的最小的$(a_i+i)$,如果有另一個$a_j$滿足$a_i\geq a_j$,那麼$(a_i+i)-(a_j+j)$就是其中一種可能的答案。 同理,在$a_i\leq a_j$的case中,我們可以用BIT紀錄每個$a_i$的值所對應的最大的$(a_i-i)$,如果有另一個$a_j$滿足$a_i\leq a_j$,那麼$(a_j-j)-(a_i-i)$就是其中一種可能的答案。 搭配離散化(coordinate compression)可以把複雜度壓到$O(n\log n)$。 關於回溯,我們可以在一個狀態中同時記錄它的來源,這樣之後就有辦法得知它是從哪裡來的值。 (code裡面的from_BIT_max、from_BIT_min陣列) ::: spoiler 官解 (niter) ```cpp= #include <bits/stdc++.h> #define loop(i,a,b) for(int i=a;i<b;i++) #define ff first #define ss second using namespace std; int n; const int maxN = ((int)(1e6)) + 5; long long best_ans = (1LL<<62); int best_i = -1, best_j = -1; long long a[maxN]; int rnk[maxN]; // for case: a[j] >= a[i] long long BIT_max[maxN]; int from_BIT_max[maxN]; void upd_max(int i){ int ind = rnk[i]; long long val = a[i] - i; for(; ind < maxN; ind += ((ind)&(-ind))){ if(val > BIT_max[ind]){ BIT_max[ind] = val; from_BIT_max[ind] = i; } } return; } void qry_max(int j){ int ind = rnk[j]; long long val = a[j] - j; for(; ind > 0; ind -= ((ind)&(-ind))){ if(val - BIT_max[ind] < best_ans){ best_ans = val - BIT_max[ind]; best_i = from_BIT_max[ind]; best_j = j; } } } void solve1(){ loop(i,0,maxN){ BIT_max[i] = -(1LL<<62); } upd_max(0); loop(i,1,n){ qry_max(i); upd_max(i); } } // for case: a[i] >= a[j] long long BIT_min[maxN]; int from_BIT_min[maxN]; void upd_min(int i){ int ind = n - rnk[i] + 1; long long val = a[i] + i; for(; ind < maxN; ind += ((ind)&(-ind))){ if(val < BIT_min[ind]){ BIT_min[ind] = val; from_BIT_min[ind] = i; } } return; } void qry_min(int j){ int ind = n - rnk[j] + 1; long long val = a[j] + j; for(; ind > 0; ind -= ((ind)&(-ind))){ if(BIT_min[ind] - val < best_ans){ best_ans = BIT_min[ind] - val; best_i = from_BIT_min[ind]; best_j = j; } } } void solve2(){ loop(i,0,maxN){ BIT_min[i] = (1LL<<62); } upd_min(0); loop(i,1,n){ qry_min(i); upd_min(i); } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cin >> n; loop(i,0,n){ cin >> a[i]; } // 離散化 vector<pair<long long, int>> uni(n); loop(i,0,n){ uni[i].ff = a[i]; uni[i].ss = i; } sort(uni.begin(), uni.end()); int cnt = 1; rnk[uni[0].ss] = 1; loop(i,1,n){ if(uni[i].ff != uni[i-1].ff) cnt++; rnk[uni[i].ss] = cnt; } // start solving problem solve1(); solve2(); // cerr << best_ans << '\n'; cout << best_i+1 << ' ' << best_j+1 << '\n'; return 0; } ``` ::: ---- ## 統計 AC: pA 11/11 pB 3/9 pC 3/9 pD 3/3 pE 4/8 pF 4/9 pG 1/1 pH 0/4 pI1 1/2 pI2 0/0 pJ 0/2 首殺: pA `snorlaxorz` 00:02 pB `hahaha1234` 00:59 pC `Aestivate` 00:21 pD `I dont know the name` 00:54 pE `Aestivate` 00:36 pF `hahaha1234` 00:10 pG `Aestivate` 00:31 pH X pI1 `Aestivate` 00:54 pI2 X pJ X