[賽記] 暑假集訓CSP提高模擬7, 8

Peppa_Even_Pig發表於2024-07-26

學長出題規律:T1簽到題,T2套路題(但沒見過),T3神奇題(賽時想的做法幾乎都是錯的),T4peppapig題

學長:今天T3防AK

peppapig:今天比賽防爆零

A. Permutations & Primes 20pts

簽到題,可惜沒有簽到;

顯然,我們要讓經過1的區間最多,所以將1放在序列中間;

除了1,就是2和3,所以我們把2和3放在兩邊,這樣就可以保證中間及不同時覆蓋兩邊且覆蓋到1的區間都是合法的;

然後隨便填,就完了;

賽時將1和2放在了一塊,導致20pts;

其實也可以將合數放中間,1放最中間,質數放兩邊,這樣也是最大的,就是需要篩一下;

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int t;
int n;
int a[500005];
bool pri[500005];
bool vis[500005];
void w(int x) {
	for (int i = 2; i <= x; i++) {
		if (!vis[i]) {
			vis[i] = true;
			pri[i] = true;
			for (int j = 2; i * j <= x; j++) {
				vis[i * j] = true;
			}
		}
	}
}
int main() {
	cin >> t;
	w(300005);
	while(t--) {
		cin >> n;
		if (n == 1) {
			cout << 1 << endl;
			continue;
		}
		if (n == 2) {
			cout << 2 << ' ' << 1 << endl;
			continue;
		}
		a[n / 2 + 1] = 1;
		a[1] = 2;
		a[n] = 3;
		int x = 4;
		for (int i = 2; i <= n / 2; i++) {
			a[i] = x;
			x++;
		}
		for (int i = n / 2 + 2; i <= n - 1; i++) {
			a[i] = x;
			x++;
		}
		for (int i = 1; i <= n; i++) {
			cout << a[i] << ' ';
		}
		cout << endl;
	}
	return 0;
}

B. 樹上游戲 47pts

原題:$ ARC116E $;

貌似又是套路題。。。

我們可以貪心的來考慮,假設現在我們所允許的不滿意度(危險度)為 $ dis $,那麼從葉子節點開始向上 $ dis $ 個長度需要有一個,然後這顆子樹我們就可以刪去不考慮了;

發現:如果知道答案,那麼驗證這個答案的合法性就變得比較容易;

做法:二分答案;

具體的,我們進行 $ dfs $,統計每個節點的危險度 $ f[x] $ 和它距離它的沒有刪去的兒子的最長距離 $ g[x] $,遍歷完這個節點的所有兒子後統計答案即可;

最後判斷當前選的點數與 $ k $ 的大小關係完成 $ check $;

賽時居然想到用找重心的方法去做,很顯然是錯的(新知識沒掌握導致的,總是會亂想是不是剛學的)。又亂搞推出了一個看起來不太對的“通式”,整了47pts;

看來現在賽時想出正解確實不容易啊。。。

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int n, k;
struct sss{
	int t, ne;
}e[1000005];
int h[1000005], cnt;
void add(int u, int v) {
	e[++cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].t = v;
}
int f[1000005], g[1000005];
int dfs(int x, int fa, int dis) {
	int res = 0;
	bool vis = false;
	f[x] = 0x3f3f3f3f;
	g[x] = -0x3f3f3f3f;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (u == fa) continue;
		vis = true;
		res += dfs(u, x, dis);
		f[x] = min(f[x], f[u] + 1);
		g[x] = max(g[x], g[u] + 1);
	}
	if (f[x] + g[x] <= dis) { //最大的危險程度可以接受,直接刪除這棵子樹;
		g[x] = -0x3f3f3f3f;
	}
	if (!vis) { //是葉子節點;
		g[x] = 0;
	}
	if (f[x] > dis) g[x] = max(g[x], 0); //危險程度太高,需要等祖先設立;
	if (g[x] == dis) { //此時必須設立,要不後代就不滿足要求了;
		res++;
		f[x] = 0;
		g[x] = -0x3f3f3f3f; //刪除這棵子樹;
	}
	return res;
}
bool ck(int x) {
	return (dfs(1, 0, x) + (g[1] >= 0)) <= k; //根節點需不需要設立;
}
int main() {
	cin >> n >> k;
	int x, y;
	for (int i = 1; i <= n - 1; i++) {
		cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	int l = 1;
	int r = n;
	int ans;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if (ck(mid)) {
			r = mid - 1;
			ans = mid;
		} else {
			l = mid + 1;
		}
	}
	cout << ans;
	return 0;
}

可能把 $ g $ 陣列設為 $ -INF $ 以刪除子樹這種操作也是一種常見套路吧;

C. Ball Collector 0pts

用到了一種資料結構,叫可撤銷並查集;

其實這種題又有一種常見套路:將 $ a_i $ 與 $ b_i $ 連邊;

其實賽時想出來了,不過是將 $ a_i $ 與 $ b_i $ 的反面連邊,然後跑 $ 2-SAT $,結果最後發現只能輸出一種可行方案,不能輸出最大值,這些專題確實趕得太緊了,沒掌握

相關文章