1. 程式人生 > 其它 >YACS-2022.6-銀組

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\)

,都需要求出刪去第 \(i\) 個數的方案數,因此考慮動態規劃。

\(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\)

開始之後的陣列成 \(t\) 的方案數,可以列舉前 \(i-1\) 個數的和,則答案為 \(\sum f[i-1,j]\times g[i+1,t-j]\)

這樣的做法時空複雜度均為 \(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;

程式碼非常簡單,不給惹。