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

Peppa_Even_Pig發表於2024-11-18

字串構造機 100pts

原題,見[賽記] 多校A層衝刺NOIP2024模擬賽01【衡中】 T1;

忍者小隊 60pts

賽時最後想出來個 $ \Theta(n^2 \log n) $ 的 DP,所以60pts;

對於這個DP,直接用 map 維護一下所有lcm的狀態轉移即可;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
int n, m;
int a[500005];
int vis[500005];
int ma;
vector<int> v[500005];
int gc[500005];
map<int, int> f;
int pri[500005], cnt;
bool vi[500005], vv[500005];
void w() {
	for (int i = 2; i <= ma; i++) {
		if (!vi[i]) {
			pri[++cnt] = i;
			for (int j = 2; j * i <= ma; j++) {
				vi[i * j] = true;
			}
		}
	}
}
int main() {
	freopen("sor.in", "r", stdin);
	freopen("sor.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		vis[a[i]]++;
		ma = max(ma, a[i]);
	}
	w();
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j * i <= ma; j++) {
			if (vis[j * i]) {
				for (int k = 1; k <= vis[j * i]; k++) v[i].push_back(j * i);
				if (gc[i] == 0) gc[i] = j * i;
				else gc[i] = __gcd(gc[i], j * i);
				int pos = lower_bound(pri + 1, pri + 1 + cnt, j) - pri;
				if (pri[pos] == j) vv[i] = true;
			}
		}
	}
	for (int i = 1; i <= m; i++) {
		if (gc[i] != i) {
			cout << -1 << ' ' << -1 << '\n';
			continue;
		}
		if (vis[i]) {
			cout << 1 << ' ' << v[i].size() << '\n';
			continue;
		}
		if (vv[i]) {
			cout << 2 << ' ' << v[i].size() << '\n';
			continue;
		}
		f.clear();
		for (int j = 0; j < v[i].size(); j++) {
			int now = v[i][j];
			if (!f[now]) f[now] = 1;
			else f[now] = min(f[now], 1);
			for (auto it = f.begin(); it != f.end(); it++) {
				int u = __gcd(it -> first, now);
				if (u < i) continue;
				if (!f[u]) f[u] = it -> second + 1;
				else f[u] = min(f[u], it -> second + 1);
			}
		}
		cout << f[i] << ' ' << v[i].size() << '\n';
	}
	return 0;
}

對於正解,考慮到答案不會很大(小於等於 $ 7 $,考慮 $ 2 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 19 = 9699690 > 600000 $ ),所以可以先列舉答案,然後判斷是否合法;

以下設 $ w $ 為值域;

判斷是否合法的問題可以轉化為方案數,設 $ f_i $ 表示當前 $ lcm = i $ 的方案數,則有轉移 $ f_i = C_{sum_i}^{t} - \sum_{j = 2}^{\lfloor \frac{w}{i} \rfloor} f_j $;

最後判斷 $ f $ 是否為 $ 0 $ 即可,這裡可以對一個比較大的質數取模(畢竟是這個質數的倍數的機率較低);

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const long long mod = 998244353;
int n, m;
int a[5000005], ma, vis[5000005];
int ans[5000005];
vector<int> v[5000005];
long long f[5000005], fac[5000005], fav[5000005];
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;
}
long long C(long long a, long long b) {
	if (a < b) return 0;
	if (b < 0) return 0;
	return fac[a] * fav[b] % mod * fav[a - b] % mod;
}
int main() {
	freopen("sor.in", "r", stdin);
	freopen("sor.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		vis[a[i]]++;
		ma = max(ma, a[i]);
	}
	fac[0] = 1;
	fav[0] = 1;
	for (int i = 1; i <= 1000000; i++) {
		fac[i] = fac[i - 1] * i % mod;
		fav[i] = ksm(fac[i], mod - 2);
	}
	for (int i = 1; i <= ma; i++) {
		for (int j = 1; j * i <= ma; j++) {
			if (vis[j * i]) {
				for (int k = 1; k <= vis[j * i]; k++) v[i].push_back(j * i);
			}
		}
	}
	for (int t = 1; t <= 7; t++) {
		for (int i = 1; i <= ma; i++) f[i] = 0;
		for (int i = ma; i >= 1; i--) {
			if (v[i].size() < t) continue;
			long long sum = 0;
			for (int j = 2; j * i <= ma; j++) {
				sum = (sum + f[i * j]) % mod;
			}
			f[i] = (C(v[i].size(), t) - sum + mod) % mod;
		}
		for (int i = 1; i <= m; i++) {
			if (f[i] != 0 && !ans[i]) {
				ans[i] = t;
			}
		}
	}
	for (int i = 1; i <= m; i++) {
		if (!ans[i]) cout << -1 << ' ' << -1 << '\n';
		else cout << ans[i] << ' ' << v[i].size() << '\n';
	}
	return 0;
}

狗卡 0pts

賽時被 $ 600005 $ 個 deque 欺騙以致於都 RE,以此警告;

deque 的空間要乘 $ 16 $ 還是多少,所以謹慎;

對於這個題,我們首先想一想每個武將只有一級的時候,那麼每次選最小即可;

如果不是一級,考慮兩個數段 $ a, b $ ,$ a $ 先於 $ b $ 當 $ \overline{a} < \overline{b} $ 時;

所以我們找每個武將的最長遞增平均值段即可,考慮插入一個數,我們讓它一直和前面的段合併直到遞增即可;

用個堆維護一下,每次出最小的,時間複雜度:$ \Theta(n \log n) $;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
long long n, m;
vector<int> v[600005];
struct sss{
	double val;
	int id, l, r;
	long long sum;
	bool operator <(const sss &A) const {
		return val > A.val;
	}
}e[1200005];
priority_queue<sss> q;
int main() {
	freopen("dog.in", "r", stdin);
	freopen("dog.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	int k, x;
	for (int i = 1; i <= n; i++) {
		cin >> k;
		v[i].push_back(0);
		for (int j = 1; j <= k; j++) {
			cin >> x;
			v[i].push_back(x);
		}
		int now = 0;
		for (int j = 1; j <= k; j++) {
			e[++now] = {1.00 * v[i][j], i, j, j, v[i][j]};
			while(now > 1 && e[now].val <= e[now - 1].val) {
				e[now - 1].r = e[now].r;
				e[now - 1].sum += e[now].sum;
				e[now - 1].val = 1.00 * e[now - 1].sum / (e[now - 1].r - e[now - 1].l + 1);
				now--;
			}
		}
		for (int j = 1; j <= now; j++) q.push(e[j]);
	}
	long long sum = 0, now = 0, ans = 0;
	while(!q.empty()) {
		sss t = q.top();
		q.pop();
		for (int i = t.l; i <= t.r; i++) {
			ans += sum * v[t.id][i];
			now += v[t.id][i];
			sum++;
		}
	}
	ans += (m - now) * sum;
	cout << ans;
	return 0;
}

相關文章