[賽記] NOIP2024加賽7

Peppa_Even_Pig發表於2024-11-24

鏡的綺想 (mirror) 100pts

考慮 $ \Theta(nm) $ 的做法,發現我們可以對於每一對實點和虛點求它們的“鏡面”,然後得到 $ \Theta(nm) $ 個“鏡面”,發現這些直線只可能是形如 $ y = 0.5x, x \in Z $ 的直線,所以我們直接乘 $ 2 $,然後開個桶統計一下即可;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n, m;
bool vis[3000005];
vector<int> shi[3000005], xu[3000005], v;
int sum[5000005];
int main() {
	freopen("mirror.in", "r", stdin);
	freopen("mirror.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	int x, y;
	for (int i = 1; i <= n; i++) {
		cin >> x >> y;
		x += 1e6;
		y += 1e6;
		shi[x].push_back(y);
		if (!vis[x]) {
			vis[x] = true;
			v.push_back(x);
		}
	}
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		x += 1e6;
		y += 1e6;
		xu[x].push_back(y);
		if (!vis[x]) {
			vis[x] = true;
			v.push_back(x);
		}
	}
	int ans = 0;
	for (int i = 0; i < v.size(); i++) {
		for (int j = 0; j < shi[v[i]].size(); j++) {
			for (int k = 0; k < xu[v[i]].size(); k++) {
				sum[(shi[v[i]][j] + xu[v[i]][k])]++;
				ans = max(ans, sum[(shi[v[i]][j] + xu[v[i]][k])]);
			}
		}
	}
	cout << ans;
	return 0;
}

萬物有靈 (animism) -pts

賽時不會獨立集,所以沒寫

考慮獨立集就是一個圖中的點集,其中任意兩點沒有邊相連;

那麼對於這棵樹,我們發現,它的最大獨立集是從最後一層開始,每隔一層選一層,最後選到根,因為每往下走,它的節點數只增不減;

這樣樸素實現是 $ \Theta(n) $ 的,考慮最佳化;

發現它有一個迴圈節,每 $ 2k $ 次就會回來,所以我們先暴力把週期翻倍,可以考慮計算迴圈節的貢獻 + 剩餘貢獻,後者直接暴力,前者可以考慮DP,設 $ f_{i} $ 表示前 $ i $ 個迴圈節的貢獻和,那麼有轉移 $ f_{i} = \prod a \times f_{i - 1} + f_1 $,就是把前面的左移一位,最後再加上 $ f_1 $;

這樣就可以矩陣加速,所以時間複雜度:$ \Theta(\log n + k) $;

細節有些多;

點選檢視程式碼
#include <iostream>
#include <cstdio>
using namespace std;
long long nn, k, mod;
long long a[2000005];
struct Mat{
	long long a[5][5];
	int n, m;
	inline void clear() {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= m; j++) {
				a[i][j] = 0;
			}
		}
	}
	inline void one() {
		for (int i = 1; i <= n; i++) a[i][i] = 1;
	}
	inline void rsize(int x, int y) {
		n = x;
		m = y;
	}
	inline Mat operator *(const Mat &A) const {
		Mat ans;
		ans.rsize(n, A.m);
		ans.clear();
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= A.m; j++) {
				for (int k = 1; k <= m; k++) {
					ans.a[i][j] = (ans.a[i][j] + a[i][k] * A.a[k][j] % mod) % mod;
				}
			}
		}
		return ans;
	}
};
Mat ksm(Mat A, long long b) {
	Mat ans;
	ans.rsize(2, 2);
	ans.clear();
	ans.one();
	while(b) {
		if (b & 1) ans = ans * A;
		A = A * A;
		b >>= 1;
	}
	return ans;
}
long long qpow(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 main() {
	freopen("animism.in", "r", stdin);
	freopen("animism.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> nn >> k >> mod;
	for (int i = 1; i <= k; i++) {
		cin >> a[i];
		a[i + k] = a[i];
	}
	k *= 2;
	if (nn <= k) {
		long long ans = 1;
		long long now = 1;
		if (!(nn & 1)) {
			ans = 1;
			for (int i = 1; i <= nn; i++) {
				now = now * a[i] % mod;
				if (!(i & 1)) ans = (ans + now) % mod;
			}
		} else {
			ans = 0;
			for (int i = 1; i <= nn; i++) {
				now = now * a[i] % mod;
				if (i & 1) ans = (ans + now) % mod;
			}
		}
		cout << ans;
		return 0;
	}
	long long w = 1, f = 0;
	for (int i = 1; i <= k; i++) {
		w = w * a[i] % mod;
		if (!(nn & 1)) {
			if (!(i & 1)) f = (f + w) % mod;
		} else {
			if (i & 1) f = (f + w) % mod;
		}
	}
	Mat B;
	B.rsize(2, 2);
	B.a[1][1] = w; B.a[1][2] = 1; B.a[2][1] = 0; B.a[2][2] = 1;
	long long res = (nn + (nn & 1)) / k;
	B = ksm(B, res - 1);
	long long now = qpow(w, res);
	long long ans = (B.a[1][1] * f % mod + B.a[1][2] * f % mod) % mod;
	if (!(nn & 1)) ans = (ans + 1) % mod;
	long long ret = nn % k;
	for (int i = 1; i <= ret; i++) {
		now = now * a[i] % mod;
		if (ret & 1) {
			if (i & 1) ans = (ans + now) % mod;
		} else {
			if (!(i & 1)) ans = (ans + now) % mod;
		}
	}
	cout << ans;
	return 0;
}

白石溪 (creek) 30pts

暴力DP還掛了15pts。。。

DP不好最佳化,考慮貪心;

我們欽定一開始全是藍色,那麼每次把一個藍色變成紅色,貢獻為 a[i] - b[i] + (i - 1) * d + (n - i) * c - now * (d + c),其中 $ now $ 是現在有的(不算他自己)的紅色個數,我們發現減號後面的一項在這個狀態下是相同的,所以直接按 a[i] - b[i] + (i - 1) * d + (n - i) * c 排序選最大即可;

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

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
long long n, c, d;
long long f[2][1000005];
long long a[1000005], b[1000005];
priority_queue<long long> q;
int main() {
	freopen("creek.in", "r", stdin);
	freopen("creek.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> c >> d;
	long long ans = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i] >> b[i];
		ans += b[i];
		q.push(a[i] - b[i] + (i - 1) * d + (n - i) * c);
	}
	long long now = 0;
	long long sum = ans;
	while(!q.empty()) {
		long long t = q.top();
		q.pop();
		sum += t - now * (d + c);
		ans = max(ans, sum);
		now++;
	}
	cout << ans;
	return 0;
}

上山崗 (uphill) 15pts

我們首先讓人從小到大選山,如果他能選就選最後面的,可以發現,這樣是可以得到一個最大解的,這個直接線段樹二分即可解決;

考慮將大數前移,不難發現,如果這個數在前一步操作中匹配上了,那麼我們只能將其移到沒有匹配的山去匹配(不會出現和小數互換的情況,因為如果能換,這個位置就是較小的那個數了)。如果沒有匹配,考慮他能不能將一個小數換掉,或者去一個空位上(前面大的數留下來的),兩者取最大即可;

開兩棵線段樹,然後線段樹二分即可解決這個問題,時間複雜度: $ \Theta(n \log n) $;

點選檢視程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
int n;
int a[500005], b[500005], pos[500005], ans[500005];
bool vis[500005];
vector<int> v;
namespace SEG{
	inline int ls(int x) {
		return x << 1;
	}
	inline int rs(int x) {
		return x << 1 | 1;
	}
	struct sss{
		int l, r, mi;
	}tr[3000005];
	inline void push_up(int id) {
		tr[id].mi = min(tr[ls(id)].mi, tr[rs(id)].mi);
	}
	void bt(int id, int l, int r) {
		tr[id].l = l;
		tr[id].r = r;
		if (l == r) {
			tr[id].mi = a[l];
			return;
		}
		int mid = (l + r) >> 1;
		bt(ls(id), l, mid);
		bt(rs(id), mid + 1, r);
		push_up(id);
	}
	void bt1(int id, int l, int r) {
		tr[id].l = l;
		tr[id].r = r;
		if (l == r) {
			if (!vis[l]) tr[id].mi = a[l];
			else tr[id].mi = 2e9;
			return;
		}
		int mid = (l + r) >> 1;
		bt1(ls(id), l, mid);
		bt1(rs(id), mid + 1, r);
		push_up(id);
	}
	int ask(int id, int d) {
		if (tr[id].l == tr[id].r) return tr[id].l;
		if (tr[rs(id)].mi < d) return ask(rs(id), d);
		else return ask(ls(id), d);
	}
	int askk(int id, int d) {
		if (tr[id].l == tr[id].r) return tr[id].l;
		if (tr[ls(id)].mi < d) return askk(ls(id), d);
		else return askk(rs(id), d);
	}
	void del(int id, int pos) {
		if (tr[id].l == tr[id].r) {
			tr[id].mi = 2e9;
			return;
		}
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (pos <= mid) del(ls(id), pos);
		else del(rs(id), pos);
		push_up(id);
	}
	void add(int id, int pos) {
		if (tr[id].l == tr[id].r) {
			tr[id].mi = a[tr[id].l];
			return;
		}
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (pos <= mid) add(ls(id), pos);
		else add(rs(id), pos);
		push_up(id);
	}
}
namespace seg{
	inline int ls(int x) {
		return x << 1;
	}
	inline int rs(int x) {
		return x << 1 | 1;
	}
	struct sss{
		int l, r, mi;
	}tr[3000005];
	inline void push_up(int id) {
		tr[id].mi = min(tr[ls(id)].mi, tr[rs(id)].mi);
	}
	void bt(int id, int l, int r) {
		tr[id].l = l;
		tr[id].r = r;
		if (l == r) {
			if (vis[l]) tr[id].mi = a[l];
			else tr[id].mi = 2e9;
			return;
		}
		int mid = (l + r) >> 1;
		bt(ls(id), l, mid);
		bt(rs(id), mid + 1, r);
		push_up(id);
	}
	void del(int id, int pos) {
		if (tr[id].l == tr[id].r) {
			tr[id].mi = 2e9;
			return;
		}
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (pos <= mid) del(ls(id), pos);
		else del(rs(id), pos);
		push_up(id);
	}
	int ask(int id, int d) {
		if (tr[id].l == tr[id].r) return tr[id].l;
		if (tr[ls(id)].mi < d) return ask(ls(id), d);
		else return ask(rs(id), d);
	}
	int askk(int id, int pos) {
		if (tr[id].l == tr[id].r) return tr[id].mi;
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (pos <= mid) return askk(ls(id), pos);
		else return askk(rs(id), pos);
		push_up(id);
	}
}
int main() {
	freopen("uphill.in", "r", stdin);
	freopen("uphill.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];
	}
	SEG::bt(1, 1, n);
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
	}
	sort(b + 1, b + 1 + n);
	for (int i = 1; i <= n; i++) {
		if (SEG::tr[1].mi >= b[i]) continue;
		pos[i] = SEG::ask(1, b[i]);
		vis[pos[i]] = true;
		SEG::del(1, pos[i]);
	}
	seg::bt(1, 1, n);
	SEG::bt1(1, 1, n);
	for (int i = n; i >= 1; i--) {
		int p = seg::askk(1, pos[i]);
		if (p == 2e9) {
			if (seg::tr[1].mi >= b[i]) {
				v.push_back(b[i]);
				continue;
			}
			int now = seg::ask(1, b[i]);
			int ma = SEG::askk(1, 1e9);
			if (now < ma) {
				ans[now] = b[i];
				seg::del(1, now);
			} else {
				ans[ma] = b[i];
				SEG::del(1, ma);
			}
		} else {
			SEG::add(1, pos[i]);
			seg::del(1, pos[i]);
			int now = SEG::askk(1, b[i]);
			ans[now] = b[i];
			SEG::del(1, now);
		}
	}
	int now = 0;
	for (int i = 1; i <= n; i++) {
		if (!ans[i]) {
			cout << v[now] << ' ';
			now++;
		} else cout << ans[i] << ' ';
	}
	return 0;
}

相關文章