cychien
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- title: IOICamp 2023 Day 3 Contest Editorial tags: editorial, competitive-programming --- # IOIC 2023 Day 3 Editorial ## Problem 301 ### SOS 為了解釋方便,我們把 $AND$ 符號改成 $\subseteq$,並且會用二進位數字表示集合。 令 $N = \lceil \log_2{n+1} \rceil$,也就是 $n$ 的二進位表示中的位數。整題的 $N \leq 20$ ### Subtask 1 ($N \leq 10$) 暴力枚舉,$O(3^N)$ 或 $O(4^N)$ 都可以過。 ### Subtask 2 滿分解 令 $g(x) = \bigoplus _ {0 \leq y \ , \ x \subseteq y = y} f(y)$ 則,$f(x) = a_x + \sum _ {0 \leq y < x \ , \ x \subseteq \ y = y} g(y)$ 令 $F[i][j]$ 代表和 $i$ 的前 $n - j$ 個位元相同的所有子集 $y$ 的 $g(y)$ 的和。 同理,$G[i][j]$ 代表和 $i$ 的前 $n - j$ 個位元相同的所有子集 $y$ 的 $f(y)$ 的和。 那麼可以枚舉 $i$ 由小到大,先計算所有 $F[i][*]$之後得到 $f(i) = F[i][N] + a_i$,再計算所有 $G[i][*]$。 轉移是: $$ F[i][j] = \begin{cases} F[i][j-1] &,i \& 2^j = 0\\ F[i][j-1] + F[i \oplus 2^j][j-1] &,\text{otherwise} \end{cases} $$ $$ G[i][j] = \begin{cases} G[i][j-1] &,i \& 2^j = 0\\ G[i][j-1] \oplus G[i \oplus 2^j][j-1] &,\text{otherwise} \end{cases} $$ 複雜度 $O(2^N N)$ :::spoiler Sample Code ```cpp= //Challenge: Accepted #pragma GCC optimize("Ofast") #include <bits/stdc++.h> using namespace std; #ifdef zisk void debug(){cout << endl;} template<class T, class ... U> void debug(T a, U ... b){cout << a << " ", debug(b...);} template<class T> void pary(T l, T r) { while (l != r) cout << *l << " ", l++; cout << endl; } #else #define debug(...) 0 #define pary(...) 0 #endif #define ll long long #define maxn 1050005 #define pii pair<int, int> #define ff first #define ss second #define io ios_base::sync_with_stdio(0);cin.tie(0); const ll inf = 1LL<<60; int fs[maxn][21], gs[maxn][21]; ll f[maxn], g[maxn]; const int se = (1<<30)-1; void madd(int &x, int y) { x = (x + y) & se; } int main() { io; int n; cin >> n; for (int i = 0;i < n;i++) cin >> f[i]; for (int i = 0;i < n;i++) { for (int j = 0;j < 20;j++) { if (j) madd(fs[i][j], fs[i][j-1]); if ((i>>j)&1) madd(fs[i][j], fs[i ^ (1<<j)][j]); } f[i] += fs[i][19]; f[i] &= se; gs[i][0] ^= f[i]; for (int j = 0;j < 20;j++) { if (j) gs[i][j] ^= gs[i][j-1]; if ((i>>j)&1) gs[i][j] ^= gs[i ^ (1<<j)][j]; } g[i] = gs[i][19]; for (int j = 0;j < 20;j++) { madd(fs[i][j], g[i]); } } cout << f[n-1] << "\n"; } ``` ::: ## Problem 302 首先可以將問題轉為對於每個 $k$ ,計算範圍有交的 $k$ 元子集有幾個 對於一堆五維長方體,它們的交集具有體積相當於它們在接下來定義的 $32$ 個平行宇宙中的交的體積乘上該宇宙的係數之和為 $1$ (否則為 $0$ ) 具體來說,對 $i$ 從 $0$ 到 $31$ ,第 $i$ 個宇宙中,原本是 $[l_1, r_1] \times[l_2, r_2] \times[l_3, r_3] \times[l_4, r_4] \times[l_5, r_5]$ 的長方體的一些 $r$ 會變成比原本小 $1$ 的數字 對 $j$ 從 $1$ 到 $5$ ,如果 $i$ 的第 $j$ 個位元是 $1$ ,那麼第 $i$ 個宇宙中所有長方體的第 $j$ 維的 $r$ 要減去 $1$ 自然的第 $i$ 個宇宙的係數是 $(-1)^{popcount(i)}$ 如此,在固定 $k$ 的時候只要對每個宇宙計算每個 $k$ 元子集的交的體積和再乘上對應的係數即可表徵有幾個 $k$ 元子集的交非空 (請自行證明) 固定當前所在的宇宙,一個 $k$ 元子集的交的體積即是要數有幾個單元正方體同時在這 $k$ 個長方體中,交換求和順序即知是要對每個單元正方體計算有幾個長方體覆蓋它 (若有 $x$ 個長方體覆蓋它,則其對答案的貢獻為 ${x} \choose {k}$ ) 可以使用五維前綴和計算,每個宇宙會花上 $O(5\times 26^5 + 2^5n + n^2)$,於是就有了一個 $O(5\times 2^5 \times 26^5 + 2^{10}n + 2^5 n^2)$ 的作法 可以發現可將所有宇宙的係數視為一體再做組合數的計算,於是變成 $O(5\times 2^5 \times 26^5 + 2^{10}n + n^2)$ 接下來注意到 $a_x$ 對於 $b_k$ 貢獻的係數是 ${x} \choose {k}$ 可以寫成一個卷積的形式,即 $(\sum_i (a_i i!)x^i)(\sum_i \frac{x^{-i}}{i!})=\sum_i (b_i i!) x^i$,於是優化多項式乘法即可 總複雜度為 $O(5\times 2^5 \times 26^5 + 2^{10}n + n \log n)$ ,足以通過 ## Problem 303 ### Subtask 1 可以觀察到,令$[a_1,a_2,..,a_k]$滿足$d_{a_1} = a_2,d_{a_2} = a_3,...,d_{a_{k - 1}} = a_k,d_{a_k} = a_1$,我們稱這叫做一個循環節。對於每個循環節,只要我們知道其中一個人往目標點移動時所走的路徑,我們就能夠逆向計算出整個循環節的移動路徑。 接下來,我們將整棵樹看成一個由左排列到右的陣列。對於每個循環節上的人,我們可以透過他們與目標點在陣列上的相對位置算出他們的初始移動方向是向左或右。可以發現到,在有其中一個人拿到鑰匙卡之前,所有人都不會改變移動方向。因此,只要找到與目標點上的人相向而行並距離最短的人,我們便可以判斷他的移動路徑是從起點直接走到目標點。這件事只需要儲存每個人在陣列上的位置,並稍微處理一些條件判斷便能完成,時間複雜度為$O(N)$。 ### Subtask 2 這個子題其實與滿分解非常相似,它主要是用來簡化題目以幫助思考。通常想到這個子題後應該能很快想到滿分解,兩者的差別在於你能肯定某個性質並好好證明,而後用於優化自己的程式碼。這邊提供一個透過純粹觀察完成此子題的思路。 首先,令$x,y,z$滿足$d_x = y, d_y = z$,則必定可以找到一個$w$,滿足$x$到$y$、$x$到$z$、$y$到$z$的路徑都經過$w$。先只考慮$w$唯一的case,計算$x$到$w$、$y$到$w$、$z$到$w$的距離為$di_x,di_y,di_z$。 如果$di_x$是最小值,那$x$就可以最先走到$w$,此時可以發現,$y$正在往$w$移動,那$x$只要和$y$相向而行,接著再往$y$移動就好。我們於是直接找到了一個人的移動路徑,稱作直接路徑。 如果$di_x$不是最小值且$di_y$是最小值,那$y$就可以最先走到$w$,此時可以發現,$x$正在往$w$移動,而$y$正要離開$w$。在這個狀況下,$y$的移動路徑會是$x$的移動路徑的一部份,因此我們可以直接不看$x$。 不過,如果$di_x$和$di_y$都不是最小值,那$x,y,z$似乎都還不能被拋棄。不過我們可以稍微想一下,在我們找到一個新的人$t$使$t$的移動路徑成為$z$的移動路徑的一部份前,我們可以暫時將$z$的路徑視為$x,y$的路徑的一部份,而暫時不看$x,y$。 那麼,我們便可以維護一個stack,這個stack裡面存有還有可能被找出直接路徑的節點,並依序檢查節點,直到順利找到一條直接路徑。 唯一的問題,就是可能會沒辦法找到直接路徑,這時我們努力觀察,你會發現,唯一可能找不到直接路徑的狀況便是每一對$x,y,z$都滿足$di_x$和$di_y$都不是最小值。那我們直接選一個適當的起點(非葉節點便可)開始檢查便可以避免這個問題。 ### Subtask 3 對於任意一個原本在點$u$上的人$p_u$,其在樹上的移動路徑有兩種。一種是從$u$直接走到點$d_u$,並恰好在路徑上遇到原本在點$d_u$上的人;另一種是先從$u$走到$v$,並在$v$遇到$p_{d_u}$,再從$v$走回$d_u$。 現在令一個序列$[v_1,v_2,...,v_k]$滿足$d_{v_1}=v_2,d_{v_2}=v_3,...,d_{v_{k-1}}=v_k,d_{v_k}=v_1$。 則在序列$[v_1,v_2,...,v_k]$之中,至少存在一個$v_i$會是第一種路徑。 證明如下: 令$w_i$代表$p_{v_i}$再走第二種路徑的情況下,離開$v_i$與$v_{(i\%k+1)}$路徑的點。 假設所有人都會走第二種路徑,則我們可以定義每個$p_{v_i}$必定處於以下三種狀態的其中一種: - 狀態1、尚未經過$w_i$、尚未遇到$p_{v_{(i\%k+1)}}$; - 狀態2、已經經過$w_i$、尚未遇到$p_{v_{(i\%k+1)}}$; - 狀態3、已經經過$w_i$、已經遇到$p_{v_{(i\%k+1)}}$。 不難發現,所有$p_{v_i}$都必定經歷狀態1$\rightarrow$狀態2$\rightarrow$狀態3,並且如果$p_{v_i}$從狀態2變成了狀態3,則$p_{v_{(i\%k+1)}}$必須先變成狀態3,然而,在這樣的限制下,沒有$p_{v_i}$有辦法變成狀態3,因此不能所有人都走第二種路徑。 那麼,假捨我們知道$p_{v_i}$走的是第一種路徑,我們便能很簡單得到$p_{v_{(i+k-1)\%k}}$的路徑,進而得到$p_{v_{(i+k-2)\%k}}$,最終得到整個序列的路徑。 那麼,剩下的唯一問題便是對於一個序列$[v_1,v_2,...,v_k]$,找到其中一個走第一種路徑的$p_{v_i}$。 對於$v_i$,令$j=(i\%k+2)$並找出一個點$r_i$,使得$r_i$到$v_i,v_{(i\%k+1)},v_j$的距離和最小。 則以這三點到$r_i$的距離可分為三種狀況: - 1.$v_i$到$r_i$的距離非嚴格最小,則$v_i$必定走第一種路徑 - 2.$v_{i+1}$到$r_i$的距離非嚴格小於$v_j$到$r_i$的距離,嚴格小於$v_i$到$r_i$的距離,則$v_i$必定走第二種路徑 - 3.$v_{j}$到$r_i$的距離嚴格最小,則我們可以將$j$視為$j\%k+1$,再計算一次同樣的問題 上面的第三種狀況處理的非常不嚴謹,可能存在一種狀況是,$v_i$能夠走第一種路徑,然而當我們遇到第三種狀況,將$j$當成其他的$j\%k+c$處理後,卻被判斷成了第二種狀況。但是我們能夠保證,即使這樣計算會忽略某些能走第一種路徑的$v_i$,我們仍至少能正確找出一個走第一種路徑的$v_i$。簡單的證明如下:假設存在這樣被我們誤判$v_i$,則代表當我們計算到某個$j$時,$p_{v_{(i\%k+1)}}$有能力先抵達$r_i$,進而先離開$v_i$到$v_{(i\%k+1)}$的路徑或是先遇到$p_{v_{(i\%k+2)}}$。在這個前提下,如果$p_{v_i}$還能再走第一種路徑並在$v_i$到$v_{(i\%k+1)}$路徑上遇到$p_{v_{(i\%k+1)}}$,則此時$p_{v_{(i\%k+1)}}$必定已經遇到$p_{v_{(i\%k+2)}}$。 那麼,如果$p_{v_{(i\%k+1)}}$走的是第二種路徑,由前面的結論我們知道在此之前必定有一個走第一種路徑的$p_{v_l}$已經遇到$p_{v_{(l\%k+1)}}$了。由此可知,對於所有會被誤判的走第一種路徑的$v_i$,必定存在一個走第一種路徑的$v_l$,滿足$p_{v_l}$遇到$p_{v_{(l\%k+1)}}$早於$p_{v_i}$遇到$p_{v_{(i\%k+1)}}$,因此,必定存在一個走第一種路徑的$p_{v_t}$最早遇到$p_{v_{(t\%k+1)}}$,使的他不會被誤判。 有了以上結論,最後我們只需要判斷目前的$v_i$屬於哪種狀況。 狀態1的話,就直接完成計算。 狀態3的話,就把$v_t$存進一個stack,期待未來的$j$來做計算 狀態2的話,就可以把$v_i$丟掉,並拿目前的$j$去計算stack裡面的$v_l$的答案,而不難發現,只要stack裡面最上面的$v_l$被計算出狀態3的結果,剩下的$v_t$也必定是狀態3。有了這個性質,我們便能保證計算次數是$O(N)$ 最後,計算的細節會牽涉到$LCA$的計算以及一些條件判斷,整體的時間複雜度將會是$O(N\log N)$ :::spoiler Sample Code ```cpp #include<bits/stdc++.h> using namespace std; #define sz(a) ((int)a.size()) int n, lca[400010][20], di[400010], l[400010], r[400010], id=1, mid[200010], des[200010], pr[200010]; bool vis[200010]; vector<int> g[400010]; void dfs(int p, int f, int d){ //bug(p,f,d); lca[p][0] = f; di[p] = d; l[p] = id++; for(int i = 0; i < 19; i++) if(lca[p][i] != -1)lca[p][i + 1] = lca[lca[p][i]][i]; for(int i: g[p]) if(i != f) dfs(i, p, d + 1); r[p] = id++; } int dis1(int x, int y, int lc){return di[x] + di[y] - 2 * di[lc];} int dis2(int x, int lc){return di[x] - di[lc];} int dis3(int x, int y){return abs(di[x] - di[y]);} bool isch(int x, int y){return l[x] < l[y] && l[y] < r[x];}//y is child of x int fdpt(int x, int d){ for(int j = 19; j >= 0; j--)if(d & (1ll << j)) x = lca[x][j]; return x; } int LCA(int x, int y){ if(di[x] > di[y]) swap(x, y); int le = di[y] - di[x]; for(int j = 19; j >= 0; j--) if(le & (1ll << j)) y = lca[y][j]; if(x == y) return x; for(int j = 19; j >= 0; j--) if(lca[x][j] != -1 && lca[x][j] != lca[y][j]) x = lca[x][j], y = lca[y][j]; return lca[x][0]; } int evl(int s, int e, int x){//0 pop 1 find 2 continue int lc1 = LCA(x, e),lc2 = LCA(s, x); if(isch(s, e)){ if(di[lc1] <= di[s]) return 1; } else if(di[lc2] >= di[s]) return 1; if(isch(e, s)){ if(di[lc2] <= di[e]) return 0; } else if(di[lc1] >= di[e]) return 0; if(isch(s, e)){ int ds = dis2(lc1, s), de = dis2(e, lc1), dx = dis2(x, lc1); if(min({ds, de, dx}) == ds)return 1; else if(min({ds, de, dx}) == de)return 0; else return 2; } else if(isch(e, s)){ int ds = dis2(s, lc2), de = dis2(lc2, e), dx = dis2(x, lc2); if(min({ds, de, dx}) == ds) return 1; else if(min({ds, de, dx}) == de) return 0; else return 2; } else{ int ds, de, dx, lc3 = LCA(s, e); if(lc1 == lc2){ ds = dis2(s, lc3), de = dis2(e, lc3), dx = dis1(lc3, x, lc1); } else if(lc1 == lc3){ ds = dis2(s, lc2), de = dis1(lc2, e, lc3), dx = dis2(x, lc2); } else{ ds = dis1(s, lc1, lc3), de = dis2(e, lc1), dx = dis2(x, lc1); } if(min({ds, de, dx}) == ds) return 1; else if(min({ds, de, dx}) == de) return 0; else return 2; } } int nxt(int x){return (x == n - 1? 0: x + 1);} int pre(int x){return (x == 0? n - 1: x - 1);} vector<int> sta; void solve(int s){ int p = -1; vector<int> tv; tv.push_back(s); vis[s] = 1; int tp = des[s]; while(tp != s){ vis[tp] = 1; tv.push_back(tp); tp = des[tp]; } if(sz(tv) == 1){ mid[tv[0]] = tv[0]; return; } if(sz(tv) == 2){ mid[tv[0]] = tv[1]; mid[tv[1]] = tv[0]; return; } for(int ii = 1; ii <= sz(tv) || sz(sta); ii++){ int i = ii % (sz(tv)); while(sz(sta)){ int re = evl(sta.back(), des[sta.back()], tv[i]); if(re == 1){p = sta.back(); break;} if(re == 2) break; sta.pop_back(); } if(p != -1)break; if(ii <= sz(tv)) sta.push_back(pr[tv[i]]); } sta.clear(); mid[p] = des[p]; int rp = p; p = pr[p]; for(; p != rp; p = pr[p]){ int u = des[p]; int tx = dis1(u, mid[u], LCA(u, mid[u])), ty = dis1(p, mid[u], LCA(p, mid[u])); if(tx >= ty){ mid[p] = u; continue; } if(tx + dis1(mid[u], des[u], LCA(mid[u], des[u])) >= dis1(p, des[u], LCA(p, des[u]))){ int tar = ty - tx; tar /= 2; tar += tx; int lc = LCA(p, mid[u]); if(dis2(p, lc) >= tar) mid[p] = fdpt(p, tar); else mid[p] = fdpt(mid[u], dis2(mid[u], lc) + dis2(p, lc) - tar); } else mid[p] = des[u]; } } signed main(){ ios_base::sync_with_stdio(0);cin.tie(0); cin >> n; for(int i = 0; i < n; i++) cin >> des[i], des[i]--, pr[des[i]] = i; for(int i = 0; i < n - 1; i++){ int x, y; cin >> x >> y; --x, --y; g[x].push_back(n + i); g[y].push_back(n + i); g[n + i].push_back(x); g[n + i].push_back(y); } dfs(0,-1,1); memset(vis, 0, sizeof(vis)); for(int i = 0; i < n; i++)if(!vis[i]) solve(i); int ans = 0; for(int i = 0; i < n; i++){ ans = max(ans, dis1(i, mid[i], LCA(i, mid[i])) + dis1(mid[i], des[i], LCA(mid[i], des[i]))); } cout << ans / 2 << '\n'; return 0; } ``` ::: ## Problem 304 講義 p295 例題 ## Problem 305 首先,可以發現合法括號子字串可以由許多個 disjoint 的 `()` 子序列組出來,舉例來說 `(())` 可以將 1、4 拆成一組,2、3 拆成一組。 ### subtask 1 有了上面的觀察可以得出一個簡單的方法求出單一括號字串的最長合法括號子序列:掃過一遍字串,沿途紀錄當前還沒被配對的左括號數量,當掃到右括號且還有沒被配到的左括號就直接把他們配起來。因此 $O(N^2)$ 的作法是先枚舉子字串的左界,之後枚舉右界的時候就可以用相同的方法一邊維護左括號數量,一邊計算出答案了。 :::spoiler 參考 code ```cpp= #include <bits/stdc++.h> using namespace std; int main () { ios::sync_with_stdio(false), cin.tie(0); int n; string s; cin >> n >> s; long long ans = 0; for (int i = 0; i < n; ++i) { int cnt = 0, cur_len = 0; for (int j = i; j < n; ++j) { if (s[j] == '(') { cnt++; } else { if (cnt) { cnt--, cur_len += 2; } } ans += cur_len; } } cout << ans << '\n'; } ``` ::: ### 滿分解 #### Claim 考慮每次將右括號跟離他最近且還沒匹配的左括號配對,則任意一個子字串的答案會是完全包含在這個子字串內的配對數量 * 2 #### 證明 首先,假設某個子字串內的配對數量為 $x$,那很顯然答案會有下界 $2x$,因此我們想要證明不存在更大的子序列長度。 我們考慮反證法,假設該子字串的最長合法括號子序列超過 $2x$。因為這是最長合法括號子序列,若將此子序列從字串中移除,會有某個前綴都是 `)`,其他都是 `(`。 接著我們使用「不在最長合法括號子序列內最左邊的 `(`」將字串分成兩半。可以發現若我們考慮上面描述的匹配方法,前半的 `(` 都一定可以被某個 `)` 配對到,後半的 `)` 都可以被某個 `(` 配對到,所以用上方的匹配方法配出來的數量會超過 $x$,這與假設矛盾,所以就證完 Claim 的正確性。 #### 作法 有了上面的 claim 作法就變得相當簡單,只需要計算出每對括號出現在幾個子字串裡面就好了,因此可以推出一個簡單的 $O(N)$ 解 另外這題也可以用比較不通靈的分治法在 $O(N \log N)$ 的時間內解決,詳細可以聽完 Day4 的分治課再自行思考 > < :::spoiler 參考 code - stack ```cpp= #include <bits/stdc++.h> using namespace std; int main () { ios::sync_with_stdio(false), cin.tie(0); int n; string s; cin >> n >> s; long long ans = 0; vector <int> stk; for (int i = 0; i < n; ++i) { if (s[i] == '(') { stk.push_back(i); } else { if (!stk.empty()) { int x = stk.back(); stk.pop_back(); ans += 1ll * (x + 1) * (n - i) * 2; } } } cout << ans << '\n'; } ``` ::: :::spoiler 參考 code - 分治 ```cpp= #include <bits/stdc++.h> using namespace std; long long solve(int l, int r, string &s) { if (r - l == 1) { return 0; } int m = l + r >> 1; long long ans = solve(l, m, s) + solve(m, r, s); vector <int> cl(r - l + 1), cr(r - l + 1); for (int i = m, cur = 0, cntl = 0, cntr = 0; i < r; ++i) { if (s[i] == '(') { cntl++; } else { if (cntl) { cntl--, cur += 2; } else { cntr++; } } ans += 1ll * cur * (m - l); cr[cntr]++; } for (int i = m - 1, cur = 0, cntl = 0, cntr = 0; i >= l; --i) { if (s[i] == ')') { cntr++; } else { if (cntr) { cntr--, cur += 2; } else { cntl++; } } ans += 1ll * cur * (r - m); cl[cntl]++; } for (int i = r - l, cnt0 = 0, cnt1 = 0; i; --i) { cnt0 += cl[i]; cnt1 += cr[i]; ans += 1ll * cnt0 * cnt1 * 2; } return ans; } int main() { ios::sync_with_stdio(false), cin.tie(0); int n; string s; cin >> n >> s; cout << solve(0, n, s) << '\n'; } ``` ::: ## Problem 306 去看題敘。 ::: spoiler Sample Code ```cpp= #include <bits/stdc++.h> const int MOD = 998244353; const int G = 3; inline int mad(int u, int v) { u += v - MOD; u += MOD & (u >> 31); return u; } inline int mul(int u, int v) { return (int) ((int64_t) u * v % MOD); } inline int mow(int x, int e) { int r = 1; while (e) { if (e & 1) r = mul(r, x); x = mul(x, x); e >>= 1; } return r; } void ntt(std::vector<int>& a) { int n = (int) a.size(), L = 31 - __builtin_clz(n); static std::vector<int> rt(2, 1); for (static int k = 2, s = 2; k < n; k *= 2, ++s) { rt.resize(n); int z[] = {1, mow(G, MOD >> s)}; for (int i = k; i < 2 * k; ++i) rt[i] = mul(rt[i / 2], z[i & 1]); } std::vector<int> rev(n); for (int i = 0; i < n; ++i) rev[i] = (rev[i / 2] | (i & 1) << L) / 2; for (int i = 0; i < n; ++i) if (i < rev[i]) std::swap(a[i], a[rev[i]]); for (int k = 1; k < n; k *= 2) { for (int i = 0; i < n; i += 2 * k) { for (int j = 0; j < k; ++j) { int z = mul(rt[j + k], a[i + j + k]), &ai = a[i + j]; a[i + j + k] = mad(ai, MOD - z); ai += (ai + z >= MOD ? z - MOD : z); } } } } std::vector<int> conv(const std::vector<int> &a, const std::vector<int> &b) { if (a.empty() or b.empty()) return {}; int s = (int) (a.size() + b.size() - 1), B = 32 - __builtin_clz(s), n = 1 << B; int inv = mow(n, MOD - 2); std::vector<int> L(a), R(b), out(n); L.resize(n), R.resize(n); ntt(L), ntt(R); for (int i = 0; i < n; ++i) out[-i & (n-1)] = mul(mul(L[i], R[i]), inv); ntt(out); return {std::begin(out), std::begin(out) + s}; } std::vector<int> inv(const std::vector<int> &a) { if (a.size() < 1) return {}; std::vector<int> b = {mow(a[0], MOD - 2)}; for (int i = 1; i < (int) a.size(); i *= 2) { auto c = conv({std::begin(a), std::begin(a) + std::min<int>(a.size(), i * 2)}, b); c.erase(std::begin(c), std::begin(c) + i); c = conv(b, c); for (int& i: c) i = (i == 0 ? 0 : MOD - i); c.resize(i); b.insert(std::end(b), std::begin(c), std::end(c)); } b.resize(a.size()); return b; } std::vector<int> divide(std::vector<int> a, std::vector<int> b) { int n = (int) a.size(), m = (int) b.size(); if (n < m) return {}; std::reverse(std::begin(a), std::end(a)); std::reverse(std::begin(b), std::end(b)); b.resize(n - m + 1); b = inv(b); auto d = conv(a, b); d.resize(n - m + 1); std::reverse(std::begin(d), std::end(d)); return d; } std::vector<int> remain(std::vector<int> a, const std::vector<int>& b, const std::vector<int>& q) { auto c = conv(b, q); if (c.empty()) return a; for (int i = 0; i < (int) a.size(); ++i) a[i] = mad(a[i], MOD - c[i]); while (!a.empty() and a.back() == 0) a.pop_back(); return a; } std::vector<int> modular(const std::vector<int>& a, const std::vector<int>& mod) { auto b = divide(a, mod); return remain(a, mod, b); } int fast_linear_recurrence(const std::vector<int>& a, const std::vector<int>& c, int64_t K) { int N = (int) a.size(); std::vector<int> x(N+1), e(N+1), mod(N + 1); x[0] = e[1] = 1; for (int i = 0; i < N; ++i) mod[i] = (c[i] == 0 ? 0 : MOD - c[i]); mod[N] = 1; for (--K; K > 0; K /= 2) { if (K & 1) x = modular(conv(x, e), mod); e = modular(conv(e, e), mod); } x.resize(N, 0); int ans = 0; for (int i = 0; i < N; ++i) ans = mad(ans, mul(a[i], x[i])); return ans; } int main() { std::cin.tie(0)->sync_with_stdio(0); std::cin.exceptions(std::cin.failbit); int N; int64_t K; std::cin >> N >> K; K++; std::vector<int> a(N), c(N); for (int& i: a) std::cin >> i; for (int& i: c) std::cin >> i; std::cout << fast_linear_recurrence(a, c, K) << '\n'; return 0; } ``` ::: ## Problem 307 (待補) ## Problem 308 相當於要將樹上的一些點配對,使得距離和最大 對於任何一個點,如果把它當作根的話,距離和就是所有點到根的距離和扣掉 兩倍的根到點對的LCA的距離和,所以答案的上界是所有點到某個點的距離和 取這個點為樹上數字存在的點的帶權重心的話可以達到這個上界 因為樹高只有對數級別,每次從根往下找重心即可 ## Problem 309 小心讀題敘,小心不要做錯事就好了 :::spoiler Sample Code ```cpp #include <bits/stdc++.h> using namespace std; map<int, string> fr; void init(){ fr[1] = "un"; fr[2] = "deux"; fr[3] = "trois"; fr[4] = "quatre"; fr[5] = "cinq"; fr[6] = "six"; fr[7] = "sept"; fr[8] = "huit"; fr[9] = "neuf"; fr[10] = "dix"; fr[11] = "onze"; fr[12] = "douze"; fr[13] = "treize"; fr[14] = "quatorze"; fr[15] = "quinze"; fr[16] = "seize"; fr[20] = "vingt"; fr[30] = "trente"; fr[40] = "quarante"; fr[50] = "cinquante"; fr[60] = "soixante"; } string translate(int x){ if (fr.find(x) != fr.end()) return fr[x]; else if (x <= 69) return translate(x / 10 * 10) + (x % 10 == 1 ? "-et-" : "-") + translate(x % 10); else if (x <= 79) return translate(60) + (x % 10 == 1 ? "-et-" : "-") + translate(x - 60); else if (x == 80) return "quatre-vingts"; else return "quatre-vingt-" + translate(x - 80); } int main(){ ios::sync_with_stdio(0); cin.tie(0); init(); int x; cin >> x; cout << translate(x) << '\n'; return 0; } ``` :::

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully