[賽記] 多校A層衝刺NOIP2024模擬賽05

Peppa_Even_Pig發表於2024-10-13

這場數數

好數(number)100pts

找三個數的和,而且允許 $ \Theta(n^2) $,那麼我們可以維護出兩個數的和,然後每次順序遍歷找這個數減去前面的某個數在任意兩個數的和中有沒有出現過,這個也是 $ \Theta(n^2) $ 的;

所以時間複雜度:$ \Theta(n^2) $,如果帶 $ \log $ 會過不去,要用桶維護;

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[500005];
int ans;
bool vis[5000005][2];
int main() {
	freopen("number.in", "r", stdin);
	freopen("number.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];
	}
	if (a[1] * 2 < 0) vis[-1 * a[1] * 2][0] = true;
	else vis[a[1] * 2][1] = true;
	for (int i = 2; i <= n; i++) {
		bool vi = false;
		for (int j = 1; j < i; j++) {
			int now = a[i] - a[j];
			if (now < 0) {
				if (vis[-1 * now][0]) {
					vi = true;
					break;
				}
			} else {
				if (vis[now][1]) {
					vi = true;
					break;
				}
			}
		}
		if (vi) ans++;
		for (int j = 1; j <= i; j++) {
			int now = a[i] + a[j];
			if (now < 0) vis[-1 * now][0] = true;
			else vis[now][1] = true;
		}
	}
	cout << ans;
	return 0;
}

SOS字串(sos)0pts

賽時容斥了3.5h不對,其實不能容斥,因為找不到正好大於等於某個數的,總是會有重複;

所以考慮DP(貌似除了容斥就是DP把);

設 $ f_{i, j, 0/1/2} $ 表示考慮前 $ i $ 位,已經匹配了 $ j $ 個 SOS ,現在匹配到了 SOS 的第一位(1),第二位(2),第三位或者其它字母(0)時的方案數;

轉移時正常轉移,但是是 $ \Theta(n^2) $ 的;

當我們寫出轉移方程時,其實會發現對於 $ j \geq 2 $ 的狀態,轉移都是重複的,所以我們不需要挨個轉移,直接統計一下即可;

時間複雜度: $ \Theta(n) $,有常數;

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
const long long mod = 1e9 + 7;
long long n;
long long f[1000005][5][5];
int main() {
	freopen("sos.in", "r", stdin);
	freopen("sos.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	if (n < 9) {
		cout << 0;
		return 0;
	}
	if (n == 9) {
		cout << 1;
		return 0;
	}
	f[0][0][0] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= 2; j++) {
			f[i][j][0] = (f[i - 1][j][0] * 25 % mod + f[i - 1][j][1] * 24 % mod + f[i - 1][j][2] * 25 % mod) % mod;
			if (j) f[i][j][0] = (f[i][j][0] + f[i - 1][j - 1][2]) % mod;
			f[i][j][1] = (f[i - 1][j][0] + f[i - 1][j][1]) % mod;
			f[i][j][2] = f[i - 1][j][1];
		}
		f[i][3][0] = (f[i - 1][3][0] * 26 % mod + f[i - 1][2][2] % mod) % mod;
	}
	cout << f[n][3][0];
	return 0;
}

集訓營的氣球(balloon)0pts

首先考慮沒有修改怎麼做,那麼我們發現這是一個揹包問題,設 $ f_{i, j} $ 表示前 $ i $ 個人,有 $ j $ 個人買了特殊氣球的方案數,轉移考慮第 $ i $ 個人買不買特殊氣球即可;

可以滾動陣列;

但是有修改怎麼辦?

暴力的思路是每次修改重新跑一遍揹包,這樣時間複雜度顯然不對;

那麼我們怎麼幹?有個東西叫做退揹包

就是做揹包的時候反著做,退揹包的時候正著做,然後把運算取反即可;

對於正確性,可以參考Luogu P4141;

所以這裡就是先強制撤銷這個數,然後在加進來;

最後用一個小容斥,用總方案數減去小於c的方案數;

所以時間複雜度;$ \Theta(nc \log mod) $;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const long long mod = 1e9 + 7;
long long ksm(long long a, long long b) {
	long long ans = 1;
	while(b) {
		if (b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}
int n, c, q;
int a[1000005], b[1000005];
long long f[1000005], tot;
void w() {
	f[0] = 1;
	tot = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = c - 1; j >= 1; j--) {
			f[j] = (f[j - 1] * a[i] % mod + f[j] * b[i] % mod) % mod;
		}
		f[0] = f[0] * b[i] % mod;
		tot = (tot * (a[i] + b[i]) % mod) % mod;
	}
}
int main() {
	freopen("balloon.in", "r", stdin);
	freopen("balloon.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> c;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> b[i];
	cin >> q;
	w();
	int s, x, y;
	for (int i = 1; i <= q; i++) {
		cin >> s >> x >> y;
		tot = tot * ksm(((a[s] + b[s]) % mod), mod - 2) % mod * ((x + y) % mod) % mod;
		long long inv = ksm(b[s], mod - 2);
		f[0] = f[0] * inv % mod;
		for (int j = 1; j < c; j++) f[j] = (f[j] - f[j - 1] * a[s] % mod + mod) % mod * inv % mod;
		long long ans = 0;
		for (int j = c - 1; j >= 1; j--) {
			f[j] = (f[j - 1] * x % mod + f[j] * y % mod) % mod;
			ans = (ans + f[j]) % mod;
		}
		f[0] = f[0] * y % mod;
		ans = (ans + f[0]) % mod;
		a[s] = x;
		b[s] = y;
		cout << (tot - ans + mod) % mod << '\n';
	}
	return 0;
}

相關文章