[賽記] NOIP2024加賽5

Peppa_Even_Pig發表於2024-11-20

暴力操作(opt)30pts

這個錯解可反悔貪心30pts;

考慮正解,我們只需考慮前 $ \frac n2 + 1 $ 小的數即可;

考慮二分出一箇中位數 $ mid $,那麼我們要讓大於它的都用最小的代價變小;

考慮如何求這個最小的代價,因為 $ \lfloor \frac{\lfloor \frac ab \rfloor}{c} \rfloor = \lfloor \frac{ab}{c} \rfloor $,我們可以預處理出所有對於除以一個數 $ i $ 的最小代價,這個可以調和級數處理,即 $ c_{i \times j} = \min(c_{i \times j}, c_i + c_j) $,然後處理完以後維護一個字尾 $ \min $ 即可;

我們還要考慮除成 $ 0 $ 的情況,我們把這種情況的最小值存到 $ c_{n + 1} $ 中,那麼我們可以得到 $ c_{n + 1} = \min_{i = 1}^{n}(c_{n + 1}, c_i + c_{\lfloor \frac ni \rfloor + 1}) $,最後再維護一個字尾 $ \min $ 即可;

然後直接 check 就沒了;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
long long n, m, k;
long long a[500005], c[500005];
bool ck(int x) {
	long long ans = 0;
	for (int i = 1; i <= n / 2 + 1; i++) {
		if (a[i] > x) ans += c[a[i] / (x + 1) + 1];
	}
	return (ans <= k);
}
int main() {
	freopen("opt.in", "r", stdin);
	freopen("opt.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
	}
	for (int i = 1; i <= m; i++) {
		cin >> c[i];
	}
	sort(a + 1, a + 1 + n);
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j * i <= m; j++) {
			c[i * j] = min(c[i * j], c[i] + c[j]);
		}
	}
	for (int i = m - 1; i >= 1; i--) c[i] = min(c[i], c[i + 1]);
	c[m + 1] = 2e9;
	for (int i = 2; i <= m; i++) {
		c[m + 1] = min(c[m + 1], c[i] + c[m / i + 1]);
	}
	for (int i = m; i >= 1; i--) {
		c[i] = min(c[i], c[i + 1]);
	}
	int l = 0;
	int r = m;
	int ans = 0;
	while(l <= r) {
		int mid = (l + r) >> 1;
		if (ck(mid)) {
			ans = mid;
			r = mid - 1;
		} else {
			l = mid + 1;
		}
	}
	cout << ans;
	return 0;
}

異或連通(xor)0pts

重點在於想到線段樹分治

然後直接考慮線段樹分治(這個題直接 $ Trie $ 上分治就行),因為發現每條邊在所有詢問中出現的都是若干段不超過 $ \log k $ 段區間中的;

考慮首先建出 $ 01Trie $,那麼這些詢問從小到大在這個 $ 01Trie $ 上是連續的;

然後考慮一條邊對於那些詢問有貢獻,可以發現,如果二進位制位上這條邊的 $ c $ 為 $ 0 $,而 $ Trie $ 上為 $ 1 $ ,異或起來就是 $ 1 $ ,$ k $ 這一位為 $ 1 $ 時可能成立,為 $ 0 $ 不成立,所以依據這個我們就可以直接給子樹打上標記,然後直接 $ Trie $ 上分治即可;

時間複雜度:$ \Theta(n \log n \log V) $,其中 $ V $ 是值域;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <map>
using namespace std;
int n, m, q, k;
int x[500005], y[500005], c[500005], fa[500005], siz[500005], s[500005];
int find(int x) {
	if (x == fa[x]) return fa[x];
	else return find(fa[x]);
}
int tot, rt;
stack<pair<pair<int, int>, int> > sk;
long long ans;
map<int, long long> mp;
int p[35];
inline void merge(int x, int y) {
	x = find(x);
	y = find(y);
	if (siz[x] < siz[y]) {
		sk.push({{y, siz[y]}, x});
		if (x == y) return;
		ans -= (1ll * siz[x] * (siz[x] - 1) / 2);
		ans -= (1ll * siz[y] * (siz[y] - 1) / 2);
		siz[y] += siz[x];
		ans += (1ll * siz[y] * (siz[y] - 1) / 2);
		fa[x] = y;
	} else {
		sk.push({{x, siz[x]}, y});
		if (x == y) return;
		ans -= (1ll * siz[x] * (siz[x] - 1) / 2);
		ans -= (1ll * siz[y] * (siz[y] - 1) / 2);
		siz[x] += siz[y];
		ans += (1ll * siz[x] * (siz[x] - 1) / 2);
		fa[y] = x;
	}
}
namespace Trie{
	struct sss{
		int ls, rs, id;
		vector<int> v;
	}tr[5000005];
	void add(int now, int &id, int d) {
		if (!id) id = ++tot;
		if (!now) {
			tr[id].id = d;
			return;
		}
		if ((d >> (now - 1)) & 1) add(now - 1, tr[id].ls, d);
		else add(now - 1, tr[id].rs, d);
	}
	void add_e(int now, int id, int x, int d) {
		if (!id || !now) return;
		int v = ((d >> (now - 1)) & 1);
		if (p[now]) {
			if (v) {
				tr[tr[id].ls].v.push_back(x);
				add_e(now - 1, tr[id].rs, x, d);
			} else {
				tr[tr[id].rs].v.push_back(x);
				add_e(now - 1, tr[id].ls, x, d);
			}
		} else {
			if (v) add_e(now - 1, tr[id].ls, x, d);
			else add_e(now - 1, tr[id].rs, x, d);
		}
	}
	void dfs(int now, int id) {
		if (!id) return;
		for (int i = 0; i < tr[id].v.size(); i++) {
			int o = tr[id].v[i];
			merge(x[o], y[o]);
		}
		if (!now) {
			mp[tr[id].id] = ans;
			for (int i = 0; i < tr[id].v.size(); i++) {
				pair<pair<int, int>, int> t = sk.top();
				sk.pop();
				if (t.first.first == t.second) continue;
				ans -= (1ll * siz[t.first.first] * (siz[t.first.first] - 1) / 2);
				siz[t.first.first] = t.first.second;
				fa[t.second] = t.second;
				ans += (1ll * siz[t.first.first] * (siz[t.first.first] - 1) / 2);
				ans += (1ll * siz[t.second] * (siz[t.second] - 1) / 2);
			}
			return;
		}
		dfs(now - 1, tr[id].ls);
		dfs(now - 1, tr[id].rs);
		for (int i = 0; i < tr[id].v.size(); i++) {
			pair<pair<int, int>, int> t = sk.top();
			sk.pop();
			if (t.first.first == t.second) continue;
			ans -= (1ll * siz[t.first.first] * (siz[t.first.first] - 1) / 2);
			siz[t.first.first] = t.first.second;
			fa[t.second] = t.second;
			ans += (1ll * siz[t.first.first] * (siz[t.first.first] - 1) / 2);
			ans += (1ll * siz[t.second] * (siz[t.second] - 1) / 2);
		}
	}
}
using namespace Trie;
int main() {
	freopen("xor.in", "r", stdin);
	freopen("xor.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> q >> k;
	for (int i = 1; i <= 32; i++) {
		p[i] = ((k >> (i - 1)) & 1);
	}
	for (int i = 1; i <= m; i++) {
		cin >> x[i] >> y[i] >> c[i];
	}
	for (int i = 1; i <= q; i++) {
		cin >> s[i];
		add(32, rt, s[i]);
	}
	for (int i = 1; i <= m; i++) {
		add_e(32, rt, i, c[i]);
	}
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
		siz[i] = 1;
	}
	dfs(32, 1);
	for (int i = 1; i <= q; i++) {
		cout << mp[s[i]] << '\n';
	}
	return 0;
}

民主投票(election)25pts

呵呵,想不到

然後直接二分答案,二分的是每個點最多被投的票數(當票數一樣時),然後得到一個答案 $ res $;

考慮對於一個點 $ x $ ,如果 $ siz_x - 1 < res $,那麼不可以,如果 $ siz_x - 1 > res $,那麼可以,如果相等,考慮 $ res - 1 $ 對於這個點合不合法,我們就一直向上傳到根,如果此時根能接收到這個票,那就可行,否則不行;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
int t;
int n;
int fa[5000005];
struct sss{
	int t, ne;
}e[5000005];
int h[5000005], cnt;
void add(int u, int v) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
int siz[5000005];
void dfs(int x) {
	siz[x] = 0;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		dfs(u);
		siz[x] += siz[u] + 1;
	}
}
int f[5000005];
int ans[5000005];
void afs(int x, int y) {
	f[x] = 0;
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		afs(u, y);
		f[x] += f[u];
	}
	f[x] = max(0, f[x] - y) + 1;
}
bool ck(int x) {
	afs(1, x);
	return (f[1] == 1);
}
void cfs(int x, int y) {
	if (siz[x] == y) {
		ans[x] = 1;
		return;
	}
	for (int i = h[x]; i; i = e[i].ne) {
		int u = e[i].t;
		if (f[u] > 1) cfs(u, y);
	}
}
int main() {
	freopen("election.in", "r", stdin);
	freopen("election.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> t;
	while(t--) {
		cin >> n;
		for (int i = 2; i <= n; i++) {
			cin >> fa[i];
			add(fa[i], i);
		}
		dfs(1);
		int l = 1;
		int r = n;
		int res = 0;
		while(l <= r) {
			int mid = (l + r) >> 1;
			if (ck(mid)) {
				res = mid;
				r = mid - 1;
			} else l = mid + 1;
		}
		for (int i = 1; i <= n; i++) {
			if (siz[i] > res) ans[i] = 1;
			else ans[i] = 0;
		}
		afs(1, res - 1);
		if (f[1] == 2) { //根自己一個 + 上傳的一個
			cfs(1, res);
		}
		for (int i = 1; i <= n; i++) {
			cout << ans[i];
		}
		cout << '\n';
		for (int i = 1; i <= n; i++) h[i] = 0;
		cnt = 0;
	}
	return 0;
}

相關文章