NOIP2022 簡要題解

前言

作爲一名退役 OIer,藉着此比賽來複健。

大部分題目都是口胡(沒啥好寫的),spj題手寫了代碼,A了。

難度不算特別高,但是賽場上拿到高分略有壓力(退役了可以瞎bb)。

個人認爲應該要拿到 300+ 的分數,但是感覺 AH 成績普遍不好?

spj 題(尤其是構造題)一定要多重視啊,我當年沒趕上這個好時代啊~

種花

洛谷標籤爲綠,pass

簽到題,如果出事了建議去 pj 組歷練。

懶得討論。

喵了個喵

貌似是照應《羊了個羊》。是一道很有意思的 cf 類型題,大概會是 div1 中決定 rank 在 100 內還是外的分界線的難度。

此題在官方數據範圍的指引下難度降了很多。首先 \(k=2n-2\) 十分容易,只需要維持一個空棧即可,其他每個棧至多兩張卡牌。上層卡牌利用方法 1,下層卡牌利用方法 2 即可。

然後思考如何解決 \(k=2n-1\)。上面的策略在遇到連續 \(2n-1\) 個不同卡牌時,就不能維持一個空棧。首先在茫茫之中要明確一個原則:所有的棧中至多 \(2n-1\) 個卡牌,更進一步,不存在兩個相同卡牌,否則將會把問題帶向深淵,愈加複雜。

當遇到這種不可調和的情況時,仔細思考發現可以分 3 類情況討論。首先先定義:棧中放入卡牌 \(X\) 後,所有棧有 \(2n-1\) 張不同卡牌,放 \(X\) 前有 \(2n-2\) 張卡牌,也就是說有 \(n-1\) 個棧,每個棧有兩張卡牌。下層的卡牌集合記爲 \(\{A\}\),上層爲 \(\{B\}\)

\(X\) 之後,必然還會遇到一次 \(X\)。在 \(X\) 之前,我們可能會遇到 \(A\)\(B\)

第一種情況:在兩次 \(X\) 之間全是 \(B\)。那麼 \(X\) 放空棧即可,對後續操作不會有影響。

假如在兩次 \(X\) 之間含有 \(A\),且第一個 \(A\) 在棧 \(i\) 中,棧 \(i\) 中還有一個上層卡牌 \(B_0\),第二種情況是:\(B_0\) 不在 \(X\)\(A\) 之間,那麼就把 \(X\) 放到第 \(i\) 個棧頂,然後後面正常操作,直到遇到 \(A\),此時空棧仍然是空棧,利用其消去 \(A\),那麼仍然存在空棧並且其它棧元素均不超過 2,可以繼續。

那麼當 \(B_0\) 出現在 \(X\)\(A\) 之間,採用上面的方法會導致棧中已有的 \(B_0\) 無法消去,於是爲第三種情況。那麼就把 \(X\) 放到空棧裏,當操作到 \(B_0\) 時,\(i\) 棧僅剩一個元素 \(A\),如果之後再次遇到 \(B_0\),就放到 \(X\) 頂上,這樣遇到了 \(A\) 以後,直接放在 \(i\) 棧即可消去 \(A\),此時 \(i\) 棧變成了空棧,並且其它棧元素均不超過 2。

複雜度 \(\mathcal O(n+m)\)

#include <bits/stdc++.h>
const int N = 305, M = 2e6 + 5;
int T, n, m, k, a[N], b[N], c[N * 2], cur, s[M];
std::stack<int> s1, s2;
struct step { int x, y, z; };
std::vector<step> ans;
void opt_1(int x) {
	ans.push_back({1, x, 0});
}
void opt_2(int x, int y) {
	ans.push_back({2, x, y});
}
bool solve(int x) {
	if (c[x]) {
		if (c[x] < 0) {
			int u = -c[x];
			opt_1(u); b[u] = 0;
			s2.push(u);
		}
		else {
			int u = c[x];
			opt_1(cur); opt_2(u, cur);
			a[u] = b[u];
			if (b[u]) {
				c[b[u]] *= -1;
				b[u] = 0;
				s2.push(u);
			}
			else s1.push(u);
		}
		c[x] = 0;
	}
	else if (!s1.empty()) {
		int u = s1.top(); s1.pop();
		opt_1(u);
		a[u] = x; c[x] = u; // 第一層 +
	}
	else if (!s2.empty()) {
		int u = s2.top(); s2.pop();
		opt_1(u);
		b[u] = x; c[x] = -u; // 第二層 -
	}
	else return false;
	return true;
}
int main() {
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d%d", &n, &m, &k);
		for (int i = 1; i <= m; i++)
			scanf("%d", &s[i]);
		while (!s1.empty()) s1.pop();
		while (!s2.empty()) s2.pop();
		ans.clear();
		cur = n;
		for (int i = 1; i < n; i++)
			s1.push(i), s2.push(i);
		for (int i = 1; i <= m; i++)
			if (!solve(s[i])) {
				int j = i + 1;
				while (s[j] != s[i] && c[s[j]] < 0) j++;
				if (s[j] == s[i]) {
					opt_1(cur);
					for (int k = i + 1; k < j; k++)
						solve(s[k]);
					opt_1(cur);
				}
				else {
					int u = c[s[j]], p = 0;
					for (int k = i + 1; k < j; k++)
						if (c[s[k]] == -u) { p = k; break; }
					if (p) {
						opt_1(cur);
						c[a[cur] = s[i]] = cur;
						for (int k = i + 1; k < p; k++)
							solve(s[k]);
						opt_1(u);
						b[u] = c[s[p]] = 0; s2.push(cur);
						for (int k = p + 1; k < j; k++)
							solve(s[k]);
						opt_1(u);
						a[u] = c[s[j]] = 0;
						cur = u;
					}
					else {
						opt_1(u);
						for (int k = i + 1; k < j; k++)
							solve(s[k]);
						opt_1(cur); opt_2(u, cur);
						c[s[j]] = 0;
						c[a[u] = b[u]] = u;
						c[b[u] = s[i]] = -u;
					}
				}
				i = j;
			}
		printf("%lu\n", ans.size());
		for (step o : ans)
			if (o.x == 1) printf("1 %d\n", o.y);
			else printf("2 %d %d\n", o.y, o.z);
	}
	return 0;
}

建造軍營

此題很裸,直接 縮點 + DP 計個數即可。沒什麼好說的。

比賽

看了眼數據範圍 + 時限,直接分塊。細節沒想好,想起來了就補。

好像可以 線段樹?不管了我 DS 很菜,想起來再補。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章