[賽記] 多校A層衝刺NOIP2024模擬賽11 && 12

Peppa_Even_Pig發表於2024-10-24

氣泡排序 100pts

比較顯然的簽到題 (好久沒這麼水過了)

考慮這個錯的氣泡排序,手模一下即可發現這個 $ +k $ 有點像以前做過的同餘系中求和的問題,於是這個題同理,用 set 維護每個同餘系的排名,最後按順序輸出即可;

對於正確性,相當於每次 $ +k $,則就相當於在一個同餘系中排序;

時間複雜度:$ \Theta(n \log n) $;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
int n, k;
int a[5000005], ans[5000005];
multiset<int> s;
int main() {
	freopen("bubble.in", "r", stdin);
	freopen("bubble.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> k;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= k; i++) {
		s.clear();
		for (int j = i; j <= n; j += k) {
			s.insert(a[j]);
		}
		for (int j = i; j <= n; j += k) {
			ans[j] = *s.begin();
			s.erase(s.begin());
		}
	}
	for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
	return 0;
}

染色 4pts

給了1G原來是用在了bitset上

轉化題意:對於最終的序列,如1 1 1 1 1 2 2 3 3 3,將其去重後變為 1 2 3,則其合法當其為原序列 $ a $ 的子序列時;

正確性很正確(但確實想不到)

那麼設 $ f_i $ 表示長度為 $ i $ 的本質不同的子序列個數,那麼最終的答案即為 $ \sum_{i = 1}^{n} C_{n - 1}^{i - 1} f_i $;

證明考慮插板法;

那麼我們的問題變為了求本質不同的子序列個數,但是子序列的任意相鄰兩項不相等;

發現原來的不太好轉移,那麼多開一維,設 $ f_{i, j} $ 表示長度為 $ i $,結尾是 $ j $ 的本質不同的子序列個數,則有轉移:$ f_{i, a_i} = \sum_{j = 1}^{m} f_{i - 1, j} \ (j \not= a_i) $;

那麼這東西是 $ \Theta(nm) $ 的,發現長度只能是 $ 0 \ 1 $,於是我們可以將 $ i $ 這一維用 bitset 最佳化,複雜度 $ \Theta(\frac{nm}{w}) $;

具體實現上需要注意一些細節,比如 $ f_{i, j} $ 的基礎為 $ 1 $,我們要加上這個 $ 1 $,再如維護 $ \sum_{j = 1}^{m} f_{i - 1, j} $ 時需要先減去 $ f_j $ 再更新 $ f_j $;

有一些技巧:模 $ 2 $ 意義下的的加減操作相當於異或(奇數 + 奇數 = 偶數,剩下的都是奇數),$ C_{n}^{m} \mod 2 = [(n \ \And \ m) = m] $(證明考慮Lucas);

點選檢視程式碼

這場掛分挺多,算是給後天攢RP了吧

Alice 和璀璨花 100pts

樹狀陣列維護最長上升子序列,時間複雜度 $ \Theta(n \log n) $;

注意要先離散化,然後發現乘積項無法離散化,不過沒有關係,直接將其賦值到比它小於等於的那個位置即可(因為求的是最長上升子序列所以沒有影響,但如果這題求得是最長不下降可能就得用權值線段樹了,時間複雜度 $ \Theta(n \log V) $,其中 $ V $ 為值域);

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
long long a[1000005], b[1000005], c[5000005], f[1000005];
int cnt;
namespace BIT{
	inline int lowbit(int x) {
		return x & (-x);
	}
	long long tr[5000005];
	void add(int pos, long long d) {
		for (int i = pos; i <= cnt; i += lowbit(i)) tr[i] = max(tr[i], d);
	}
	long long ask(int pos) {
		long long ans = 0;
		for (int i = pos; i; i -= lowbit(i)) ans = max(ans, tr[i]);
		return ans;
	}
}
using namespace BIT;
long long ans;
int main() {
	freopen("alice.in", "r", stdin);
	freopen("alice.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		c[++cnt] = a[i];
	}
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
		c[++cnt] = b[i];
	}
	c[++cnt] = 0;
	c[++cnt] = -1;
	sort(c + 1, c + 1 + cnt);
	cnt = unique(c + 1, c + 1 + cnt) - c - 1;
	for (int i = 1; i <= n; i++) {
		f[i] = ask(lower_bound(c + 1, c + 1 + cnt, a[i]) - c - 1) + 1;
		ans = max(ans, f[i]);
		int pos = lower_bound(c + 1, c + 1 + cnt, a[i] * b[f[i]]) - c;
		if (c[pos] != a[i] * b[f[i]]) pos--;
		add(pos, f[i]);
	}
	cout << ans;
	return 0;
}

David 與和諧號 8pts

呵呵,部分分暴搜,正解就換了種搜+剪了個枝;

好吧暴搜掛完了,36pts -> 8pts,調參少了一個;

正解是迭代加深搜尋,並且剪了個枝;

發現我們的搜尋樹可能會很深,但答案很淺,所以可以迭代加深搜尋(IDA*);

所謂IDA*,即每次固定一個搜尋上界(設為 $ up $ ),超了就返回;

對於這個題,還有一個剪枝,就是噹噹前搜尋到的深度的估價函式 $ D(x) $ 加上當前搜尋位置大於我們所規定的上界時就返回;

$ D(x) $ 怎麼算?發現當前狀態中,如果相鄰兩項 $ a_i, a_{i - 1} $ 的差 $ | a_i - a_{i - 1} | \geq 2 $ 就至少要轉一次。那麼記這種狀態的出現次數為 $ sum $,則 $ D(x) = sum $;

所以當 $ D(x) + x > up $ 時就返回,這樣就能過了;

時間複雜度:$ \Theta(能過) $;

注意在判斷 $ D(x) + x > up $ 時先判斷 $ x > up $ 的情況,或者直接在更新答案之前判斷 $ D(x) + x > up $ 的情況也行;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int t;
int n;
int a[55];
int ans;
void dfs(int x, int ls, int up, int sum) {
	if (ans <= x - 1) return;
	if (x - 1 > up) return;
	bool vis = true;
	for (int i = 1; i <= n; i++) {
		if (a[i] != i) {
			vis = false;
			break;
		}
	}
	if (vis) {
		ans = min(ans, x - 1);
		return;
	}
	if (x - 1 + sum > up) return;
	for (int i = 2; i <= n; i++) {
		if (i == ls) continue;
		int now = sum + ((i < n) && (abs(a[i] - a[i + 1])) == 1) - ((i < n) && abs(a[1] - a[i + 1]) == 1);
		reverse(a + 1, a + 1 + i);
		dfs(x + 1, i, up, now);
		reverse(a + 1, a + 1 + i);
	}
}
int main() {
	freopen("david.in", "r", stdin);
	freopen("david.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> t;
	while(t--) {
		cin >> n;
		for (int i = 1; i <= n; i++) cin >> a[i];
		bool vis = true;
		for (int i = 1; i <= n; i++) {
			if (a[i] != i) {
				vis = false;
				break;
			}
		}
		if (vis) {
			cout << 0 << '\n';
			continue;
		}
		int sum = 0;
		for (int i = 2; i <= n; i++) {
			if (abs(a[i] - a[i - 1]) >= 2) sum++;
		}
		ans = 0x3f3f3f3f;
		int i = 1;
		while(ans == 0x3f3f3f3f && i <= 2 * n + 2) {
			dfs(1, 0, i, sum);
			i++;
		}
		cout << ans << endl;
	}
	return 0;
}

相關文章