# 2022 建中校內賽複賽題解 --- ## pA 買項鍊 Setter: 8e7 ---- 定位:前面子題很好拿的梗題(?) 但是沒人想寫 qwq ---- 拆包裝:給一張 $n$ 點 $m$ 邊的無向圖,每條邊有權重 $w_i$,一個好的邊集可以被分成若干個歐拉環,權重是每條邊的 $w_i$ 乘起來,問所有好的邊集的權重總和。 以下都用 $n$ 點跟 $m$ 邊來說明本題 ($k$ 跟 $n$ 是為了讓他看起來比較不像圖論www) ---- ### Subtask 1: $m \leq 20$ 直接枚舉邊集,判斷是不是好的 ---- 如何判斷一個好的邊集? * 一個歐拉環的每個點度數都是偶數 - 所有點度數都是偶數就能把邊拆成一些歐拉環 ---- ### Subtask 2: $n \leq 19$ ---- 每個點度數都是偶數的限制? 對於一個邊集,我們需要的資訊是每個點度數的**奇偶性**。 ---- 位元 DP! $dp[c][S]$ 代表考慮了前 $c$ 條邊,每個點度數的奇偶性是 $S$ 的權重和。 轉移:把兩個點的位元flip。 初始狀態:$dp[0][0]$ = 1,最後答案會是 $dp[m][0] - 1$ 複雜度 $O(2^nm)$ ---- ### Subtask 3: $n \leq 23$ ---- 觀察前面的 DP ,可以發現到加邊的順序不會影響答案。而且每次轉移時只會改兩個位置。 那麼如果我已經加完了 $1$ 號點連接的所有邊,最後點度還是奇數的話,他就不可能對答案有貢獻! ---- 先把點 $1$ 相鄰的邊加完,再加點 $2$, 點 $3$ ... 相鄰的邊。 每次把前面不重要的狀態忽略掉,狀態數會少一半! 時間是 $2^nn + 2^{n-1}(n-1) + \cdots \leq 2^nn(1 + \frac{1}{2} + \frac{1}{4} + \cdots) = O(2^nn)$ ---- 毒瘤另解 (by wiwiho) 把點分成 19+4個,先把兩邊算完之後對中間的邊 DP。 不知道會不會過,預期解都要跑 1~1.5秒了 ---- AC code ```cpp= vector<pii> adj[maxn]; ll f[1<<maxn], g[1<<maxn]; int main() { io int n, m; cin >> n >> m; for (int i = 0;i < m;i++) { int u, v, w; cin >> u >> v >> w; u--, v--; if (v < u) swap(u, v); adj[u].push_back({v, w}); } f[0] = g[0] = 1; for (int a = 0;a < n;a++) { for (auto [b, w]:adj[a]){ int u = n - 1 - a, v = n - 1 - b; for (int i = 0;i < (1<<(n-a));i++) { g[i] = (g[i] + f[i ^ (1<<u) ^ (1<<v)] * w) % mod; } for (int i = 0;i < (1<<(n-a));i++) f[i] = g[i]; } } cout << (f[0] + mod - 1)%mod << "\n"; } ``` --- ## pB 調分 Setter: 8e7 ---- 定位:有包裝的簡單題? ---- ### Subtask 1: $m = 1$ ---- 沒有包含所有人的話那就是無限大 有的話就是$w_i$ ---- ### Subtask 2: $m = n - 1, k_i = 2$ ---- 圖論觀點:把兩個人的分數限制看成圖上的邊 如果不連通就是 $-1$。連通的話那一定是一棵樹! ---- 把 $1$ 號點當作是根,那跟我連到的人分數都能差到最大可能值(邊權),所以直接在樹上 dfs 找距離最大值。 ---- ### Subtask 3: $k_i = 2$ ---- 延續剛剛的想法,這會變成一張無向帶權圖。 如果我把每個人的分數叫做 $d_v$,那麼一條邊$(u, v, w)$的限制代表 $d_u \leq d_v + w, d_v \leq d_u + w$ 這不就是最短路嗎?!?! ---- 差分約束:可以用最短路問題對一些變數的大小關係求解。 答案會是從點 $1$ 開始跑 Dijkstra 之後距離最遠者。為什麼? ---- ### Subtask 4: 無其他限制 ---- 現在問題變成:有一張無向圖,一開始是空的,接著有 $m$ 次加邊操作,每次選一個集合的點並讓他們兩兩之間連上權重為 $w_i$ 的邊,求單點源最短路。 ---- 解一:建圖 對一個集合的點 $v_1, v_2, \cdots, v_k$ 兩兩之間連邊等價於把 $v_1, v_2, \cdots, v_k$ 跟一個新的點 $x$ 連權重為 $\frac{w}{2}$的邊(或是連有向邊也可以)。總共的邊數是 $O(\sum k_i)$ ---- 解二:直接做? 對於每一點紀錄他在哪些群組裡,並且紀錄一個群組有沒有被「走過」,在Dijkstra 看一個新的點的時候,枚舉他在的群組,並且對沒有走過的群組鬆弛,並且將他們標記成有走過。 其實跟解一是一樣的意思,但常數比較小xd。 ---- AC code ```cpp= const ll inf = 1LL<<60; bool done[maxn]; vector<pii> adj[maxn]; vector<int> vert[maxn], col[maxn]; ll dis[maxn], wei[maxn]; int main() { io int n, m; cin >> n >> m; for (int i = 0;i < m;i++) { int ki, w; cin >> ki >> w; wei[i] = w; for (int j = 0;j < ki;j++) { int x; cin >> x; vert[i].push_back(x); col[x].push_back(i); } } for (int i = 1;i <= n;i++) dis[i] = inf; dis[1] = 0; priority_queue<pii, vector<pii>, greater<pii> > pq; pq.push({dis[1], 1}); while (pq.size()) { auto [d, cur] = pq.top(); pq.pop(); if (d != dis[cur]) continue; for (int i:col[cur]) { if (done[i]) continue; done[i] = 1; for (int v:vert[i]) { if (dis[v] > d + wei[i]) { dis[v] = d + wei[i]; pq.push({dis[v], v}); } } } } ll ans = 0; for (int i = 1;i <= n;i++) ans = max(ans, dis[i]); if (ans == inf) cout << -1 << "\n"; else cout << ans << "\n"; } ``` ---- 公審時間 ---- ```cpp= #include<bits/stdc++.h> #define int long long using namespace std; signed main(){ priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq; pq.push(make_pair(0,1)); pair<int,int> cur; vector<int> distance(n+1); vector<bool> visited(n+1,0); distance[1]=0; visited[1]=1; for(int i=2;i<=n;i++) distance[i]=1e18+1; while(!pq.empty()){ cur=pq.top(); pq.pop(); visited[cur.second]=1; for(auto x:vec[cur.second]){ if(visited[x.first]==1) continue; if(distance[x.first]<=distance[cur.second]+x.second){ continue; } else{ distance[x.first]=distance[cur.second]+x.second; pq.push(make_pair(distance[x.first],x.first)); } } } int maxi=-1; for(int i=1;i<=n;i++) maxi=max(distance[i],maxi); cout<<maxi; } ``` --- ## pC 方塊王與 Meme X Setter: alvingogo ---- 走路題,只是我沒想到他那麼慘 ---- subtask 1: 枚舉每條路徑,求出他的 mex,再把這個 mex 放進路徑上每個點的集合,最後再對集合取 mex 即可 O(n^2) 條路徑 $\cdot$ O(n) 求 mex = O(n^3) ---- subtask 2: 考慮定根為 i,我想求出 i -> 每個 j 的 memex 是多少,那就以 i 為根 dfs 一次,可以發現小孩的 memex 一定比祖先大,維護 cnt 陣列進去的時候 +1 出來的時候 -1 要算一條路徑的 mex 就從父親的 mex 開始枚舉,可以發現總共枚舉次數 <= n 於是整個演算法是 O(n^2) ---- subtask 3: 樹是一條鏈 a[0] = 0 的話答案就是 0 只要維護目前的左界和右界,可以發現右界以右的答案都會是右界自己,左界類似,而只要下一個點不是向外擴張剩下的點答案就是他 ---- subtask 4: 結合 subtask 2 跟 subtask 3,以 0 為根 假設 0 -> a -> ... -> 1 那麼對於 a 的子樹的所有不在 0 到 1 的路徑上的點,他的答案都是 2 1 的子樹的點答案是 1 ---- 接下來從 2 開始跑 可以發現只要有三條路徑就會燒雞 四種情況: 1. 當前的點 i 已經考慮過了,且不是 1 的子樹 -> 剩下的答案都是 i+1 2. 當前的點 i 已經考慮過了 且是某個以她小的點的子樹 -> 不變 3. 當前的點 i 尚未考慮過,且是根到某個已經考慮過的點的路徑上 -> 剩下的答案都是 i 4. 當前的點 i 尚未考慮過,且不滿足 3. -> 他的子樹都是 i,剩下所有不在路徑上的點的子樹都是 i+1 --- ## pD 花花世界 Setter: cheissmart ---- ~~定位: 你以為 DP 優化你都學過了嗎~~ ---- 問題等價於 $flower_i = max_{j \in [b_i-k+1, b_i]}(flower_j + (a_i - a_j + 1) * m_i)$ ---- Subtask1: 暴力轉移,~~為什麼會有人零分~~ 複雜度 $O(nk)$ ---- Subtask2: 因為 $a_i = 0$,所以$flower_i = max_{j \in [b_i-k+1, b_i]}(flower_j) + m_i$ 然後 $b_i = i - 1$,所以這是 sliding window 最大值問題,可以用單調對列做到 $O(n)$ 或是線段樹$O(nlogn)$都可以過 ---- Subtask3: 跟 subtask2 一樣但是少了 $b_i = i - 1$ 這個條件,可以用單調對列加一層離線做到 $O(n)$ 或是線段樹$O(nlogn)$都可以過 ---- Subtask4: $n \leq 10^5$ 分治/線段樹 + 李超/動態凸包 $O(nlog^2n)$ ---- # 滿分解 ---- [提示](https://www.youtube.com/watch?v=MDw82gjWJbg) ---- 如果你認真體會這首歌,不難想到做法 ---- Final remark: * 不要問我 1020050909 是啥 * > 曾經以為會寫 卻又發現假解 這是今天的第六題 比賽裡的題目 好像應該學過 我不會 快告訴 我作法 --- ## pE 數數大師 Setter: FHVirus, 8e7 ---- 定位:很好喇分的神題(?) ---- ### Subtask 1,2: $k=1, 2$ 亂做 ---- ### Subtask 3,4: $k = 3, \sum n \leq 500$, $k = 4, \sum n \leq 100$ 直接枚舉 ---- ### Subtask 6, 8: $k = 3, 4$ 且 $\sum n \leq 3000$ 少枚舉一點東西,枚舉兩項然後想辦法用公式算剩下的部份 ---- ### Subtask 4, 5, 6, 8, 9: $\sum n \leq 3000$ DP! ---- 令 $dp[i][j]$ 代表選了 $i$ 個東西,且總和模 $n$ 的餘數是 $j$ 的方法數。 複雜度是 $O(n^2k)$,看起來好像不太會過但是其實常數蠻小的 ---- ### Subtask 7: $k = 3$ ---- 打表觀察,哪次不打表的 (X) 可以發現答案似乎有個很好的規律: 如果 $n \% 3 \neq 0$,答案就是$\binom{n}{3}/n$,否則是 $\lfloor \binom{n}{3} /n \rfloor +1$ 這是真的 ---- ### Subtask 10: 無其他限制 ---- 如果你繼續打表通靈的話,可能會看出一個規律。 考慮固定 $n, k$,令陣列 $a_i$ 為總和模 $n$ 餘 $i$ 的方法數。則$a_i = a_{i + gcd(n, k)}$ 也太唬爛了吧 :angry: ---- 證明:對於一個集合 $\{v_1, v_2, \cdots, v_k \}$ ,我們可以把他對應到 $\{v_1 +1, v_2 + 1, \cdots, v_k + 1 \}$(在模 $n$ 底下),這樣的話總和也會增加 $k$。如果重複這個操作 $n$ 次的話就會得到原本的序列,根據貝祖定理,這些操作中的總和必須跟原本的和在模 $gcd(n, k)$ 底下同餘,因此方法數也會相同。 ---- 因此,我們可以令 $dp[i][j]$ 為有多少種方法選 $i$ 個東西使得總和模 $gcd(n, k)$ 是 $j$。在轉移的時候一次把所有 $a + b \times gcd(n, k)$ 的數字加進來考慮。因為 $gcd(n, k) \leq k$,複雜度會是 $O(k^4)$。 ---- AC code ```cpp= const int MOD = 1e9 + 7; inline int mad(int u, int v) { u += v - MOD; u += MOD & (u >> 31); return u; } inline int mul(int u, int v) { return (uint64_t) u * v % MOD; } inline int mow(int x, int e) { int r = 1; for (; e; x = mul(x, x), e >>= 1) if (e & 1) r = mul(r, x); return r; } int sol3(int N, int K) { int g = __gcd(N, K); int num = N / g; debug(N, K, g, num); vector<vector<int>> dp(K + 1, vector<int>(g, 0)); vector<int> numCk(K + 1, 1); for (int i = 1; i <= K; ++i) numCk[i] = mul(numCk[i-1], mul(num-i+1, mow(i, MOD-2))); dp[0][0] = 1; for (int v = 0; v < g; ++v) { for (int k = K - 1; k >= 0; --k) { for (int i = 0; i < g; ++i) { if (dp[k][i] == 0) continue; for (int dk = 1, x = i; dk + k <= K and dk <= num; ++dk) { x += v; if (x >= g) x -= g; int& to = dp[k + dk][x]; to = mad(to, mul(dp[k][i], numCk[dk])); } } } } return mul(dp[K][0], mow(N / g, MOD-2)); } int main() { cin.tie(0)->sync_with_stdio(0); cin.exceptions(cin.failbit); int T; cin >> T; while (T --> 0) { int N, K; cin >> N >> K; cout << sol3(N, K) << '\n'; } return 0; } ``` --- 北市賽加油! 沒進的也不要太難過><
{}
    608 views
   owned this note