YACS-2022.6-銀組
中途吃了個飯做了個核酸,壓線賽時 AK 了,希望不要掛分。
T1 子集和
題目描述
子集和問題是指,給定 \(n\) 個數字 \(a_1,a_2,\cdots, a_n\),再給定一個目標 \(t\),有多少種方法,能夠選出一些數字,使得它們的和等於 \(t\)。注意每個數字可以重複選擇多次。
小愛希望計算一些帶有限制的子集和問題,她想知道,對於 \(1\le i\le n\),如果規定不能選擇 \(a_i\),那麼還有多少種方法,可以選出一些數字,使得它們的和等於目標 \(t\)?答案對 \(10^9+7\) 取模。
\(n\le 5000, a_i,t\le 10^4\)。
大體思路
對於 \(1\le i\le n\)
記 \(f[i,j]\) 表示前 \(i\) 個數之和為 \(j\) 的方案數,\(f[0,0]=1\)。由於每個數可以重複選取,\(f[i,j]=f[i-1,j]+f[i,j-a_i]\),其中外層迴圈 \(i=1\sim n\),內層迴圈 \(j=0\sim m\)。
同時,記 \(g[i,j]\) 表示 \(i\) 開始之後的數之和為 \(j\) 的方案數,\(g[n+1,0]=1\)。其狀態轉移方程與 \(f\) 類似但 \(i\) 的方向相反。
容易想到,對於刪去 \(a_i\),相當於前 \(i-1\) 個數和 \(i+1\)
這樣的做法時空複雜度均為 \(O(nt)\),時間上能夠通過,但即使開 int
也可能 MLE,因此將 \(g\) 利用滾動陣列優化,並且算出一組 \(g\) 後立刻計算相應的 \(ans\),這樣空間常數得到優化,用 int
可以通過。
部分程式碼
f[0][0] = 1; rep(i, 1, n) { rep(j, 0, m) { f[i][j] = f[i - 1][j]; if(j >= a[i]) f[i][j] = (f[i][j] + f[i][j - a[i]]) % mod; } } g[(n + 1) & 1][0] = 1; Rep(i, n, 1) { rep(j, 0, m) { g[i & 1][j] = g[(i + 1) & 1][j]; if(j >= a[i]) g[i & 1][j] = (g[i & 1][j] + g[i & 1][j - a[i]]) % mod; ans[i] = (ans[i] + 1ll * f[i - 1][j] * g[(i + 1) & 1][m - j] % mod) % mod; } } rep(i, 1, n) writeln(ans[i]);
T2 路徑問題
題目大意
給定一張圖,有 \(n\) 個點,每個點有且只有一個後繼,第 \(i\) 個點的後繼編號為 \(t_i\),這條邊的權重為 \(w_i\)。
給定一個整數 \(k\),從每個點出發,沿著給定的後繼遍歷,經過 \(k\) 條邊後停止,請分別求出這些路徑上的最大權重。
\(n,k\le 5\times 10^5,\ w\le 10^9\)。
大體思路
如果你想到了許多複雜度做法,你可能已經用到了 LCA,那你為什麼沒有想到倍增呢?
倍增簡單題,如果整個圖是鏈狀,那麼顯然是 ST 表的模板。現在有了後繼,只需要一個輔助陣列。
因此,我們定義 \(f[i,p]\) 表示節點 \(i\) 出發經過 \(2^p\) 條邊得到的最大權值,\(g[i,p]\) 表示 \(i\) 的 \(2^p\) 級後繼。
那麼顯然有 \(f[i,0]=w_i,g[i,0]=t_i\);
f[i][p] = max(f[i][p - 1], f[g[i][p - 1]][p - 1]);
g[i][p] = g[g[i][p - 1]][p - 1];
對於 \(k\),列舉二進位制位,如果 \(k\) 的這一位為 \(1\),那麼 \(f[i,p]\) 產生貢獻,並且令 \(i\) 指向 \(2^p\) 級後繼。
read(n); read(k);
rep(i, 1, n) read(g[i][0]);
rep(i, 1, n) read(f[i][0]);
int P = log(k) / log(2) + 1;
rep(p, 1, P) {
rep(i, 1, n) {
f[i][p] = max(f[i][p - 1], f[g[i][p - 1]][p - 1]);
g[i][p] = g[g[i][p - 1]][p - 1];
}
}
rep(i, 1, n) {
int ans = 0, u = i;
rep(p, 0, P) if((k >> p) & 1)
ans = max(ans, f[u][p]), u = g[u][p];
writeln(ans);
}
T3 航海探險
就是這道題卡了我一會兒,然後我去做核酸了。
題目大意
在大海中,有 \(n\) 座不確定座標的島嶼。接下來,會陸續發現這些島嶼之間的相對位置關係,在發現的過程中,系統也會詢問一些島嶼之間的距離:
-
若有兩島嶼的相對位置關係被發現了,則輸入格式為
D a b e
,表示島嶼 \(a\) 在島嶼 \(b\) 的 \(D\) 方向 \(e\) 公里。\(D\) 必須是ESWN
中的一個字母,分別表示東南西北四個方向,輸入資料保證不會有矛盾的情況發生,但有些資訊可能是重複多餘的。 -
如果輸入格式為
? a b
,表示需要查詢島嶼 \(a\) 在島嶼 \(b\) 之間的距離。如果 \(a\) 與 \(b\) 之間的相對關係尚不確定,則輸出?
。
注意,在計算距離時,所使用的定義是橫縱座標的差的絕對值之和,這種距離定義被稱為城市距離或曼哈頓距離,而非常用的歐幾里得距離。
大體思路
其實這道題如果是歐幾里得距離也能做。
\(a,b\) 之間的位置關係有傳遞性和方向性,因此考慮用帶權並查集維護向量。
用 \(v_X\) 記錄節點 \(X\) 指向其父親節點 \(Y\) 的向量 \(\overrightarrow{XY}\)。在路徑壓縮時由於路徑壓縮改變父親節點,設當前節點為 \(X\),父親節點為 \(Y\),根節點為 \(R\),則 \(\overrightarrow{XR}=\overrightarrow{XY}+\overrightarrow{YR}\),而 \(\overrightarrow{YR}\) 在上一級函式中已經計算完成,因此直接令當前的 \(v_X\) 加上 \(v_Y\) 即可。
合併時,對於已經在同一個集合內的節點不予操作。否則,假設兩個節點為 \(a,b\),其所在集合的根節點分別為 \(R_a,R_b\)。根據 D a b e
語句,我們可以得到向量 \(\overrightarrow{ab}\),現在要更新 \(\overrightarrow{R_a R_b}\)。顯然有 \(\overrightarrow{R_aR_b}=\overrightarrow{R_aa}+\overrightarrow{ab}+\overrightarrow{bR_b}=v_b-v_a+\overrightarrow{ab}\)。
曼哈頓距離就是兩個向量的 \(|\Delta x|+|\Delta y|\)。
另外,\(e\le 10^9\),需要開 long long
。
int n, m, fa[maxn];
struct Vector {
int x, y;
} v[maxn];
inline int find(int u) {
if(u == fa[u]) return u;
int rt = find(fa[u]);
v[u].x += v[fa[u]].x, v[u].y += v[fa[u]].y;
return fa[u] = rt;
}
inline void merge(int x, int y, int dir, int dis) {
// Dir x y dis, 0~3 = ESWN
int f1 = find(x), f2 = find(y);
if(f1 == f2) return;
fa[f1] = f2;
int dx = v[y].x - v[x].x, dy = v[y].y - v[x].y;
if(dir == 0) v[f1] = {dx - dis, dy};
else if(dir == 1) v[f1] = {dx, dis + dy};
else if(dir == 2) v[f1] = {dis + dx, dy};
else v[f1] = {dx, dy - dis};
}
signed main () {
read(n); read(m);
rep(i, 1, n) fa[i] = i;
while(m --) {
char op; int a, b, d;
ReadChar(op);
if(op == '?') {
read(a); read(b);
int f1 = find(a), f2 = find(b);
if(f1 != f2) puts("?");
else writeln(abs(v[a].x - v[b].x) + abs(v[a].y - v[b].y));
}
else {
read(a); read(b); read(d);
if(op == 'E') merge(a, b, 0, d);
else if(op == 'S') merge(a, b, 1, d);
else if(op == 'W') merge(a, b, 2, d);
else merge(a, b, 3, d);
}
// rep(i, 1, n) printf("%lld: (%lld, %lld) ", i, v[i].x, v[i].y);
// putchar('\n');
}
return 0;
}
T4 沒有考試的天數
題目大意
給定 \(n\) 個數 \(a_i\),求 \(1\sim t\) 之中有多少個數不是任何一個 \(a_i\) 的正整數倍。
\(n\le 20,t\le 10^{14}\)
大體思路
容斥模板題。用一個 \(n\) 位的二進位制數表示是否選 \(a_i\)。
每次對於所有選擇的數求一個 \(lcm\),然後根據數的個數的奇偶性判斷答案加上或者減去 \(\left\lfloor \dfrac{t}{lcm}\right\rfloor\)。時間複雜度 \(O(2^nn\log a )\),看上去似乎時間很緊,但其實其中的 \(n\) 是跑不滿的。更精確的計算可以列舉 \(1\) 的個數然後用組合數導,但沒啥意義。
需要注意的是,\(n\) 個 \(a_i\) 的最大公約數可能超過 long long
的範圍,因此當求得的 \(lcm>t\) 時直接 break;
。
程式碼非常簡單,不給惹。