[賽記] 多校A層衝刺NOIP2024模擬賽16 && 17

Peppa_Even_Pig發表於2024-11-04

四捨五入 100pts

對於一個數 $ x $ ,可以發現它的答案可以分成兩部分,一部分在 $ [2x + 1, n] $ 範圍內,一部分在小於它的數的範圍內,前者 $ \Theta(1) $ 算,對於後者,我們發現滿足這個要求的數 $ y $ 有 $ x \mod y < w(x, y) $ ( $ w(x, y) $ 定義為如果 $ x \mod y = 0 $,則 $ w(a, b) = \frac xy - 1 $,否則 $ w(a, b) = \lfloor \frac xy \rfloor $ );

對於每個數,可以處理出所有小於它的滿足後者的答案,這個可以差分維護;

複雜度是調和級數的,$ \Theta(n \ln n) $;

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int n;
long long a[5000005];
long long w(long long x, long long y) {
	if (x % y == 0) return x / y - 1;
	else return x / y;
}
int main() {
	freopen("count.in", "r", stdin);
	freopen("count.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	long long sum = 0;
	for (long long i = 1; i <= n; i++) {
		for (long long j = i; j <= n; j += i) {
			a[j]++;
			a[j + w(i, 2) + 1]--;
		}
		sum += a[i];
		cout << sum + max(0ll, 1ll * n - 1ll * (2 * i)) << ' ';
	}
	return 0;
}

填算符 10pts

發現 $ \And $ 放前面不劣,所以把所有 $ \And $ 放在前面可以得到一個最大的答案;

考慮順次倒著填 $ \And $,那麼我們只需判斷當前所得到的答案與能夠忍受的二進位制位上的最小答案是否在二進位制位上有交集即可;

我們需要維護一段 $ \And $,然後再一段 $ | $,再一個 $ \And $,前面的一段 $ \And $ 直接字首和維護,中間的一段 $ | $ 用線段樹維護即可;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <set>
using namespace std;
int n, k;
long long a[5000005], b[5000005];
long long ans;
set<int> s;
namespace SEG{
	inline int ls(int x) {
		return x << 1;
	}
	inline int rs(int x) {
		return x << 1 | 1;
	}
	struct sss{
		int l, r;
		long long sum;
	}tr[5000005];
	inline void push_up(int id) {
		tr[id].sum = (tr[ls(id)].sum | tr[rs(id)].sum);
	}
	void bt(int id, int l, int r) {
		tr[id].l = l;
		tr[id].r = r;
		if (l == r) {
			tr[id].sum = a[l];
			return;
		}
		int mid = (l + r) >> 1;
		bt(ls(id), l, mid);
		bt(rs(id), mid + 1, r);
		push_up(id);
	}
	long long ask(int id, int l, int r) {
		if (l > r) return 0;
		if (tr[id].l >= l && tr[id].r <= r) return tr[id].sum;
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (r <= mid) return ask(ls(id), l, r);
		else if (l > mid) return ask(rs(id), l, r);
		else return (ask(ls(id), l, mid) | ask(rs(id), mid + 1, r));
	}
}
int main() {
	freopen("bitop.in", "r", stdin);
	freopen("bitop.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];
	}
	ans = a[1];
	b[1] = a[1];
	for (int i = 2; i <= n; i++) {
		b[i] = (b[i - 1] & a[i]);
	}
	for (int i = 2; i <= k + 1; i++) {
		ans &= a[i];
	}
	for (int i = k + 2; i <= n; i++) {
		ans |= a[i];
	}
	SEG::bt(1, 1, n);
	int res = k;
	long long now = 0;
	for (int i = n - 1; i >= 1; i--) {
		if (res >= i) break;
		if (res == 0) break;
		now = b[res];
		now = (now | SEG::ask(1, res + 1, i));
		now = (now & a[i + 1]);
		if ((now & ans) == ans) {
			s.insert(i);
			res--;
		} else {
			ans ^= (a[i + 1] & ans);
		}
	}
	for (int i = 1; i <= res; i++) cout << i << ' ';
	for (auto it = s.begin(); it != s.end(); it++) cout << *it << ' ';
	return 0;
}

網格 22pts

DP;

部分分暴搜但是沒寫?

考慮我們需要維護的東西,有以前的答案,到當前位的末尾的數(不完全)與以前的答案的並,這是一個大體的思路;

考慮先乘再加的性質,我們以加為分割點,把所有的乘算出來相加即可;

所以設 $ f_{i, j}, g_{i, j}, h_{i, j}, w_{i, j} $ 分別表示以前已經確定的答案,最後一個數與前面數的乘積,截止到上一個加號所有數(除了最後一個)的乘積,到 $ (i, j) $ 的路徑數,轉移分三種情況,具體見程式碼;

注意最後一個陣列,我們要考慮所有路徑的和,所以注意在更新 $ h_{i, j} $ 時首先應該乘上 $ w_{i, j} $;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
const long long mod = 998244353;
int n, m;
char s[2005][2005];
long long f[2005][2005], g[2005][2005], h[2005][2005], w[2005][2005];
int main() {
	freopen("grid.in", "r", stdin);
	freopen("grid.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> s[i][j];
		}
	}
	h[1][1] = 1;
	w[1][1] = 1;
	g[1][1] = (s[1][1] - '0');
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (i == 1 && j == 1) continue;
			f[i][j] = (f[i - 1][j] + f[i][j - 1]) % mod;
			g[i][j] = (g[i - 1][j] + g[i][j - 1]) % mod;
			h[i][j] = (h[i - 1][j] + h[i][j - 1]) % mod;
			w[i][j] = (w[i - 1][j] + w[i][j - 1]) % mod;
			if (s[i][j] == '*') {
				h[i][j] = g[i][j];
				g[i][j] = 0;
			}
			if (s[i][j] == '+') {
				f[i][j] = (f[i][j] + g[i][j]) % mod;
				h[i][j] = w[i][j];
				g[i][j] = 0;
			}
			if (s[i][j] >= '0' && s[i][j] <= '9') {
				g[i][j] = (g[i][j] * 10 % mod + (s[i][j] - '0') * h[i][j] % mod) % mod;
			}
		}
	}
	cout << (f[n][m] + g[n][m]) % mod;
	return 0;
}

矩形 48pts

可以聯想到以前做過的找四個頂點不完全相同的矩形個數,此題同理,可以 $ \Theta(nm \log n) $ 做拿到48pts好像可以去掉 $ \log $ 但沒有細想

考慮正解,發現有小常數的部分分,於是聯想到 $ \Theta(n \sqrt n) $ 或 $ \Theta(n \log^2 n) $ 的做法;

繼承暴力的思路,以 $ x $ 為陣列下標,將所有縱座標等於 $ x $ 的點分為一類,那麼我們發現:

  1. 對於一個 $ size $ 較大的類個數不會太多,我們直接開一個桶暴力遍歷維護一下即可;

  2. 對於 $ size $ 較小的類,其中包含的點數不會太多,所以我們直接處理出所有點對,然後 Hash一下,開一個 map 記一下即可;

所以根號分治,時間複雜度 $ \Theta(n \sqrt n) $,常數很大,要用cc-hash-table;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <map>
#include <algorithm>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
int n;
int sq;
vector<int> v[200005], a, b;
bool t[200005], vis[200005];
long long ans;
cc_hash_table<long long, long long> mp;
int main() {
	freopen("rect.in", "r", stdin);
	freopen("rect.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	sq = sqrt(n);
	int x, y;
	for (int i = 1; i <= n; i++) {
		cin >> x >> y;
		v[x].push_back(y);
	}
	for (int i = 1; i <= n; i++) {
		if (v[i].size() >= sq) a.push_back(i);
		else if (v[i].size()) b.push_back(i);
		sort(v[i].begin(), v[i].end());
	}
	for (int i = 0; i < a.size(); i++) {
		vis[a[i]] = true;
		for (int j = 0; j < v[a[i]].size(); j++) {
			t[v[a[i]][j]] = true;
		}
		for (int j = 1; j <= n; j++) {
			if (!v[j].size() || vis[j]) continue;
			long long sum = 0;
			for (int k = 0; k < v[j].size(); k++) {
				if (t[v[j][k]]) sum++;
			}
			ans += (sum * (sum - 1)) / 2;
		}
		for (int j = 0; j < v[a[i]].size(); j++) {
			t[v[a[i]][j]] = false;
		}
	}
	for (int i = 0; i < b.size(); i++) {
		for (int j = 0; j < v[b[i]].size(); j++) {
			for (int k = j + 1; k < v[b[i]].size(); k++) {
				long long now = v[b[i]][j] * 10000000000 + v[b[i]][k];
				ans += 1ll * mp[now];
				mp[now]++;
			}
		}
	}
	cout << ans;
	return 0;
}

相關文章