2021清北學堂國慶刷題班Day4
比賽總結
T1
T2
$ 40 pts $
由於 $ n \leq 20 $ , $ 2 ^ {20} $ 大小可以接受, 則可以考慮直接 $ floyd $ 暴力求解任意兩點之間的最短路,暴力計算結果即可。
$ 80 pts $
忘了……
$ 100 pts $
觀察性質可以發現,一張圖的最短路樹即為其最小生成樹。
那麼顯然可以考慮先求出一張圖的最小生成樹,然後在最小生成樹上求解。
考慮在最小生成樹上如何求出任意兩點間最短路的長度和。考慮每一顆子樹的根, 從該點到該點上面那個點之間的路徑被經過的次數即為子樹內節點的個數於子樹外節點個數的乘積。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<vector> #include<queue> #include<map> #include<set> const int MAXN = 200005; const int INF = 0x7fffffff; const int MOD = 1e9 + 7; typedef long long ll; int n, m, cnt; int f[MAXN], vis[MAXN]; long long ans = 0; struct node{ int to, val; node *nxt = NULL; } edge[MAXN << 1], *head[MAXN]; struct Node{ int u, v, val; } pre[MAXN]; template <class I> inline void read(I &x){ x = 0; int f = 1; char ch; do { ch = getchar(); if(ch == '-') f = -1; } while(ch < '0' || ch > '9'); do { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); } while(ch >= '0' && ch <= '9'); x *= f; return; } inline void add_edge(int x, int y, int z){ ++cnt; edge[cnt].nxt = head[x]; head[x] = &edge[cnt]; head[x]->to = y; head[x]->val = z; return; } int find(int x){ if(f[x] == x) return x; else return f[x] = find(f[x]); } inline bool cmp(Node x, Node y) { return x.val < y.val; } void kruskal(void){ int tot = 0; std::sort(pre + 1, pre + m + 1, cmp); for(int i = 1; i <= n; ++i) f[i] = i; for(int i = 1; i <= m; ++i){ int a = find(pre[i].u), b = find(pre[i].v); if(a != b){ f[a] = b; ++tot; add_edge(pre[i].u, pre[i].v, pre[i].val); add_edge(pre[i].v, pre[i].u, pre[i].val); } if(tot == n - 1) break; } return; } inline ll ksm(ll a, ll b){ ll res = 1, bas = a; while(b){ if(b & 1) res = (res * bas) % MOD; bas = (bas * bas) % MOD; b >>= 1; } return res; } int DFS(int u, int f){ int s = 1; for(node *i = head[u]; i != NULL; i = i->nxt){ int v = i->to; if(v != f){ int s1 = DFS(v, u); ans = (ans + ksm(2, i->val) * s1 * (n - s1) % MOD) % MOD; s += s1; } } return s; } int main(){ // freopen("b11.in", "r", stdin); // freopen("my.out", "w", stdout); read(n), read(m); for(int i = 1; i <= m; ++i){ read(pre[i].u), read(pre[i].v); pre[i].val = i; } kruskal(); DFS(1, 0); printf("%lld\n", ans); return 0; }
T3
官方做法(DP):
$ f[i][j] $ 表示 $ 1 \sim i $ 被殺, $ i + 1 $ 只沒有被殺, $ j $ 次隨機。
$ g[i] $ 表示 $ i $ 存活並且 $ i $ 是存活中編號最小的皮克敏的方案數。
\[g[i] = f[i - 1][j] \times A_{n - i}^{j} \]$ h[i] $ 表示 $ i $ 存活並且 $ i $ 是存活的編號最小的且編號大於 $ i $ 皮克敏的存活的方案數。
\[h[i] = f[i - 1][j] \times A_{n - i - 1}^{j} \]第 $ i $ 只皮克敏存活的方案數:
其他做法(Minus):
$ f[i][j] $ 場上還剩 $ i $ 個,這些皮克敏中第 $ j $ 個活到最後的概率。
目標 $ f[n][i] \times i $ 。
1.先殺一個編號最小的: 當且僅當 $ (n - i) $ $ mod $ $ (k + 1) = 0 $。
此時 $ f[i][1] = 0 $, 否則 $ f[i][j] = f[i - 1][j - 1] $ 。
2.隨機 $ k $ 次殺一個:
\[f[i][j] = \frac {j - 1}{i} \times f[i - 1][j - 1] + \frac {i - j}{i} \times f[i - 1][j] \]邊界 $ f[r][i] = 1 $ , 其中 $ r = n $ $ mod $ $ (n + 1) $ 。
T4
記 $ s[i] = \sum_{i = 1}^{n} {a_i}$ , 則一隻皮克敏做完前 $ i $ 道工序 $ s_i \times b_1 - s_{i - 1} \times b_2 ... $
\[f[x][y] = {max}_{1 \leq i \leq n } \{s_i \times b_1 - s_{i - 1} \times b_2 \} \]\[\frac {f(x,y)}{y} = {max}_{1 \leq i \leq n} \{ s_{i - 1} \times \frac {x}{y} + a_i \times \frac {x}{y} - s_{i - 1} \} \]\[\frac {f(x,y)}{y} = {max}_{1 \leq i \leq n} \{ \frac{x}{y} \times s_i - s_{i - 1} \} \]把 $ \frac {x}{y} $ 看作 $ x $ , $ s_i $ 看作 $ k $, $ s_{i - 1} $ 看作 $ b $ , 則可以化簡出類似於 $ y = kx + b $ 的直線形式。
\[c_i = s_i \times x - s_{i - 1} \times y \]\[\frac {x}{y} -> k \]\[s_i \times x = s_{i - 1} \times y + c_i \]\[s_i = \frac {x}{y} s_{i - 1} + \frac {c_i}{x} \]轉化為誰和 $ y $ 軸交點座標值最大。
可以看作, 平面上有若干個點, 每個點都有一條過該點的直線, 求和 $ y $ 軸截距最大的。
考慮凸包,由於資料隨機,凸包上最多 $ 20 \sim 30 $ 個點, 直接暴力求凸包上的點求解即可。
也可以貪心求解,由於所有數為正, 則斜率為正, 按 $ y $ 與 $ x $ 從大到小排序, 選前 $ 100 $ 個點暴力,資料隨機情況下可以獲得較高的分數。