[赛记] 多校A层冲刺NOIP2024模拟赛09 && 10

排列最小生成树 (pmst) 50pts

又是诈骗题,然后又不会。。。

暴力很暴力,直接建个完全图跑 Kruskal 即可;

正解考虑如果我们连接编号相邻的点,那么每个边的边权都小于 $ n $ 真能考虑到吗?

所以我们最终的最小生成树中的边边权都小于 $ n $;

那么对于 $ |p_i - p_j| \times |i - j| < n $ 的限制,我们肯定不会出现 $ |p_i - p_j| > \sqrt n $ 且 $ |i - j| > \sqrt n $ 的情况,所以我们连的边要么是 $ |p_i - p_j| \leq \sqrt n $ ,要么是 $ |i - j| \leq \sqrt n $,(注意这里可以不同时满足);

所以我们在连边的时候只需将原数组按编号和 $ p $ 分别排序,然后对于每个点向右连 $ \sqrt n $ 条边即可;

那么总边数为 $ \Theta(n \sqrt n) $,但是在 Kruskal 过程中需要排序,如果 sort 的话会带 $ \log $ 过不去,但是发现边权很小,所以直接桶排序即可;

时间复杂度:$ \Theta(n \sqrt n) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n;
int b[500005], a[500005];
struct sas{
	unsigned short f, t, w;
	bool operator <(const sas &A) const {
		return w < A.w;
	}
}e[23000005];
long long len(int x, int y) {
	return 1ll * abs(x - y) * abs(b[x] - b[y]);
}
int cnt;
void add(int u, int v, unsigned short ww) {
	e[++cnt].f = u;
	e[cnt].t = v;
	e[cnt].w = ww;
}
int fa[500005];
int find(int x) {
	if (x != fa[x]) fa[x] = find(fa[x]);
	return fa[x];
}
long long Kru() {
	long long ans = 0;
	sort(e + 1, e + 1 + cnt);
	int sum = 0;
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= cnt; i++) {
		if (sum == n - 1) break;
		int x = find(e[i].f);
		int y = find(e[i].t);
		if (x != y) {
			fa[x] = y;
			ans += 1ll * e[i].w;
		}
	}
	return ans;
}
int main() {
	freopen("pmst.in", "r", stdin);
	freopen("pmst.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> b[i];
		a[b[i]] = i;
	}
	int sq = sqrt(n);
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= min(n, i + 1 + sq); j++) {
			if (len(i, j) <= n) add(i, j, len(i, j));
			if (len(a[i], a[j]) <= n) add(a[i], a[j], len(a[i], a[j]));
		}
	}
	cout << Kru();
	return 0;
}

卡牌游戏 (cardgame) 100pts

签到题居然在T2。。。

看到特殊性质有 $ gcd(n, m) = 1 $,于是很自然的想到从 $ gcd $ 出发;

这里我们默认 $ n < m $;

手模一下,可以发现,对于互质的情况,$ a $ 中每个数都会与 $ b $ 中每个数对战恰好一次;

对于不互质的情况,我们可以发现,$ a_i $ 是从 $ b_{a_i \mod gcd(n, m)} $ 开始对战,并且 $ b $ 的下标每次加 $ gcd(n, m) $,对战次数为 $ \frac{n \times m}{n \times cnt_{now}} $,其中 $ cnt_{now} $ 代表对战的 $ b_i $ 中不同的 $ i $ 的个数;

于是我们维护一下从 1 到 gcd(n, m) 开始,每次下标加 gcd(n, m) 的数组,然后同时维护一下 $ cnt $ 数组即可;

查询直接二分查找;

时间复杂度 $ \Theta(n \log n) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
long long n, m;
long long a[500005], b[500005];
long long cnt[100005];
vector<long long> v[100005];
long long gc;
long long lose, win, he;
bool vis;
int main() {
	freopen("cardgame.in", "r", stdin);
	freopen("cardgame.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];
	}
	for (int i = 1; i <= m; i++) {
		cin >> b[i];
	}
	if (n > m) {
		vis = true;
		for (int i = 1; i <= n; i++) swap(a[i], b[i]);
		swap(n, m);
	}
	gc = __gcd(n, m);
	for (int i = 1; i <= gc; i++) {
		for (int j = i; j <= m; j += gc) {
			v[i].push_back(b[j]);
			cnt[i]++;
		}
		sort(v[i].begin(), v[i].end());
	}
	for (int i = 1; i <= n; i++) {
		long long now = i % gc;
		if (now == 0) now = gc;
		long long val = (n * m) / (n * cnt[now]);
		long long lpos = lower_bound(v[now].begin(), v[now].end(), a[i]) - v[now].begin();
		win += lpos * val;
		long long rpos = upper_bound(v[now].begin(), v[now].end(), a[i]) - v[now].begin();
		if (lpos < v[now].size() && v[now][lpos] == a[i]) {
			he += (rpos - lpos) * val;
		}
		if (rpos < v[now].size()) {
			lose += (v[now].size() - rpos) * val;
		}
	}
	if (vis) {
		cout << lose << '\n' << win << '\n' << he;
	} else {
		cout << win << '\n' << lose << '\n' << he;
	}
	return 0;
}

比特跳跃 (jump) 39pts

$ s = 1 $ 特殊性质和暴力39pts;

考虑正解,对于这个题,其实有三个子问题;

  1. $ s = 1 $;

$ x \ \And \ y $ 这个最好考虑,因为加的边权不是 $ 0 $ 就是 $ k $;

首先我们将 $ 1 $ 与其它点连边保证联通,然后将二的次幂全都连出来一条链(因为边权都是 $ 0 $),然后枚举每个点,找到这个点二进制是 $ 0 $ 的位置并与相应的二的次幂连边,然后我们就将所有需要的的 $ 0 $ 的边权都连上了,然后从 $ 1 $ 出发的 $ 0 $ 或 $ k $ 的边权也连上了,最后跑最短路算法即可;

  1. $ s = 2 $;

对于 $ x \oplus y $ 的情况,我们发现只更改一位是最优的,所以对于每个点,只连二进制位有一位不同的点即可;

  1. $ s = 3 $;

对于 $ x | y $ 的情况,可以发现如果不是直接从 $ 1 $ 走过来,那么就由它的子集走过来;

所以我们先将 $ 1 $ 与其它点连边跑一遍最短路,然后顺序更新只改一位的子集,存起来更新即可;

注意这里的更新有 $ DP $ 的感觉,虽然只改了一位,但是存下来的却是很多位,具体看代码;

上面三个子问题都连了 $ \Theta(n \log n) $ 条边,所以时间复杂度:$ \Theta((n \log n + m) \log(n \log n + m)) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
int n, m, s;
long long k;
long long p[25];
struct sss{
	int t, ne;
	long long w;
}e[5000005];
int h[5000005], cnt;
void add(int u, int v, long long ww) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
	e[cnt].w = ww;
}
long long dis[500005], f[500005];
bool vi[5000005];
bool vis[500005];
void dij(int x, bool is) {
	priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
	if (is) {
		memset(dis, 0x3f, sizeof(dis));
	} else {
		for (int i = 2; i <= n; i++) q.push({dis[i], i});
	}
	memset(vis, false, sizeof(vis));
	q.push({0, x});
	dis[x] = 0;
	while(!q.empty()) {
		int xu = q.top().second;
		q.pop();
		if (vis[xu]) continue;
		vis[xu] = true;
		for (int i = h[xu]; i; i = e[i].ne) {
			int u = e[i].t;
			if (dis[u] > dis[xu] + e[i].w) {
				dis[u] = dis[xu] + e[i].w;
				q.push({dis[u], u});
			}
		}
	}
}
int main() {
	freopen("jump.in", "r", stdin);
	freopen("jump.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m >> s;
	cin >> k;
	int x, y;
	long long w;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		cin >> w;
		add(x, y, w);
		add(y, x, w);
	}
	if (n <= 800) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				if (i == j) continue;
				if (s == 1) add(i, j, k * 1ll * (i & j));
				if (s == 2) add(i, j, k * 1ll * (i ^ j));
				if (s == 3) add(i, j, k * 1ll * (i | j));
			}
		}
		dij(1, true);
		for (int i = 2; i <= n; i++) {
			cout << dis[i] << ' ';
		}
		return 0;
	}
	p[0] = 1;
	vi[0] = true;
	for (int j = 1; j <= 18; j++) {
		p[j] = p[j - 1] * 2;
		vi[p[j]] = true;
	}
	if (s == 1) {
		for (int j = 18; j >= 0; j--) {
			if (p[j] == n) {
				for (int i = 2; i <= n; i++) {
					cout << 0 << ' ';
				}
				return 0;
			}
		}
		for (int j = 1; j <= 18; j++) {
			if (p[j] > n) break;
			add(p[j - 1], p[j], 0);
			add(p[j], p[j - 1], 0);
		}
		for (int i = 2; i <= n; i++) {
			if (vi[i]) continue;
			add(1, i, k * 1ll * (1 & i));
			add(i, 1, k * 1ll * (1 & i));
		}
		for (int i = 2; i <= n; i++) {
			if (vi[i]) continue;
			for (int j = 0; j <= 18; j++) {
				if (p[j] > n) break;
				if (!((1 << j) & i)) {
					add(p[j], i, 0);
					add(i, p[j], 0);
				}
			}
		}
		dij(1, true);
		for (int i = 2; i <= n; i++) {
			cout << dis[i] << ' ';
		}
		return 0;
	}
	if (s == 2) {
		for (int i = 1; i <= n; i++) {
			for (int j = 0; j <= 18; j++) {
				if (p[j] > n) break;
				add(i, (i ^ (1 << j)), k * p[j]);
				add((i ^ (1 << j)), i, k * p[j]);
			}
		}
		dij(1, true);
		for (int i = 2; i <= n; i++) {
			cout << dis[i] << ' ';
		}
		return 0;
	}
	if (s == 3) {
		memset(f, 0x3f, sizeof(f));
		f[1] = 0;
		for (int i = 2; i <= n; i++) {
			add(1, i, k * (1 | i));
			add(i, 1, k * (1 | i));
		}
		dij(1, true);
		for (int i = 2; i <= n; i++) {
			for (int j = 0; j <= 18; j++) {
				if (i & (1 << j)) {
					dis[i] = min(dis[i], f[i ^ (1 << j)] + k * i);
					f[i] = min(dis[i], f[i ^ (1 << j)]);
				}
			}
		}
		dij(1, false);
		for (int i = 2; i <= n; i++) cout << dis[i] << ' ';
	}
	return 0;
}

区间 (interval) 15pts

暴力扫 $ \Theta(n^3) $ 15pts;

对于 $ \Theta(n^2) $ 左右的做法,考虑 $ n $ 很小,所以我们可以预处理出所有区间的答案,然后 $ \Theta(n) $ 询问即可;

怎样预处理?对于每一个数维护它的右边第一个大于等于它的数,那么右端点的范围就确定了,直接预处理 $ b_r > a_l $ 的个数即可;

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, q;
int a[500005], b[500005], c[1000005], cnt;
int pos[500005];
int ans[5005][5005];
int main() {
	freopen("interval.in", "r", stdin);
	freopen("interval.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];
		c[++cnt] = a[i];
	}
	for (int i = 2; i <= n; i++) {
		cin >> b[i];
		c[++cnt] = b[i];
	}
	sort(c + 1, c + 1 + cnt);
	cnt = unique(c + 1, c + 1 + cnt) - c - 1;
	for (int i = 1; i <= n; i++) {
		pos[i] = n;
		for (int j = i + 1; j <= n; j++) {
			if (a[j] >= a[i]) {
				pos[i] = j;
				break;
			}
		}
	}
	cin >> q;
	int l = 0, r = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = i + 1; j <= pos[i]; j++) {
			ans[i][j] = ans[i][j - 1];
			if (b[j] > a[i]) ans[i][j]++;
		}
	}
	for (int i = 1; i <= q; i++) {
		cin >> l >> r;
		int sum = 0;
		for (int j = l; j <= r - 1; j++) {
			sum += ans[j][min(pos[j], r)];
		}
		cout << sum << '\n';
	}
	return 0;
}

岛屿 5pts

居然都不给暴搜的部分分。。。

正解,考虑 $ DP $,然后懒了,直接粘题解把。。。

image

就是考虑每次加一对点,然后将其与已经加过的点连边,注意计算概率;

最后的求和其实就是递归一层层往下套得来的;

时间复杂度:$ \Theta(n) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <iomanip>
using namespace std;
int x, y, n;
double ans;
int main() {
	freopen("island.in", "r", stdin);
	freopen("island.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> x >> y;
	for (int i = 1; i <= y; i++) ans += 1.00 / (2 * x + i);
	for (int i = 1; i <= x; i++) ans += 1.00 / (2 * i - 1);
	cout << fixed << setprecision(15) << ans;
	return 0;
}

最短路 26pts

重测了好多次,然后就整了个26pts;

首先建出最短路树,然后考虑删一条点 $ x $ 和其父亲 $ fa_x $ 连的边能有什么次小的方案;

发现并不怎么好搞,所以我们枚举每一条非树边,看看它能有什么贡献

那么对于非树边 $ (u, v) $,当它们两个不在同一个子树时可以产生贡献;

简而言之,它们可以对树上 $ u, v $ 两点路径上的所有点,但不包括 $ lca(u, v) $ 的点产生贡献,设这些点为 $ k $,那么贡献为 $ dis_u + w_{u, v} + dis_{v} - dis_k $ (就是 $ 1 \rightarrow u \rightarrow v \rightarrow k $ ,反之同理);

对于一个点 $ k $ ,$ dis_k $ 是定值,那么我们只需要维护 $ \min (dis_u + w_{u, v} + dis_{v}) $ 即可,这是一个区间修改单点查询的问题,用树剖 + 线段树解决;

时间复杂度:$ \Theta(n \log^2 n) $;

点击查看代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
int n, m;
struct sss{
	int t, ne;
}e[500005];
int h[500005], cnt;
void add(int u, int v) {
	e[++cnt].t = v;
	e[cnt].ne = h[u];
	h[u] = cnt;
}
vector<pair<int, long long> > v[500005];
long long dis[500005];
bool vis[500005];
int pre[500005];
void dij(int x) {
	for (int i = 1; i <= n; i++) {
		dis[i] = 0x3f3f3f3f3f3f3f3f;
		vis[i] = false;
	}
	priority_queue<pair<long long, int>, vector<pair<long long, int> >, greater<pair<long long, int> > > q;
	while(!q.empty()) q.pop();
	dis[x] = 0;
	q.push({0, x});
	while(!q.empty()) {
		int xu = q.top().second;
		q.pop();
		if (vis[xu]) continue;
		vis[xu] = true;
		for (int i = 0; i < v[xu].size(); i++) {
			int u = v[xu][i].first;
			if (dis[u] > dis[xu] + v[xu][i].second) {
				dis[u] = dis[xu] + v[xu][i].second;
				pre[u] = xu;
				q.push({dis[u], u});
			}
		}
	}
}
int fa[500005], siz[500005], dep[500005], hson[500005], htop[500005], dfn[500005], dcnt;
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 mi, lz;
	}tr[500005];
	inline void push_down(int id) {
		if (tr[id].lz != 0x3f3f3f3f3f3f3f3f) {
			tr[ls(id)].lz = min(tr[ls(id)].lz, tr[id].lz);
			tr[rs(id)].lz = min(tr[rs(id)].lz, tr[id].lz);
			tr[ls(id)].mi = min(tr[ls(id)].mi, tr[id].lz);
			tr[rs(id)].mi = min(tr[rs(id)].mi, tr[id].lz);
			tr[id].lz = 0x3f3f3f3f3f3f3f3f;
		}
	}
	void bt(int id, int l, int r) {
		tr[id].l = l;
		tr[id].r = r;
		tr[id].mi = tr[id].lz = 0x3f3f3f3f3f3f3f3f;
		if (l == r) return;
		int mid = (l + r) >> 1;
		bt(ls(id), l, mid);
		bt(rs(id), mid + 1, r);
	}
	void add(int id, int l, int r, long long d) {
		if (tr[id].l >= l && tr[id].r <= r) {
			tr[id].lz = min(tr[id].lz, d);
			tr[id].mi = min(tr[id].mi, d);
			return;
		}
		push_down(id);
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (l <= mid) add(ls(id), l, r, d);
		if (r > mid) add(rs(id), l, r, d);
	}
	long long ask(int id, int pos) {
		if (tr[id].l == tr[id].r) return tr[id].mi;
		push_down(id);
		int mid = (tr[id].l + tr[id].r) >> 1;
		if (pos <= mid) return ask(ls(id), pos);
		else return ask(rs(id), pos);
	}
}
namespace TCS{
	void dfs1(int x, int f) {
		fa[x] = f;
		hson[x] = -1;
		siz[x] = 1;
		dep[x] = dep[f] + 1;
		for (int i = h[x]; i; i = e[i].ne) {
			int u = e[i].t;
			if (u == f) continue;
			dfs1(u, x);
			siz[x] += siz[u];
			if (hson[x] == -1 || siz[hson[x]] < siz[u]) hson[x] = u;
		}
	}
	void dfs2(int x, int t) {
		htop[x] = t;
		dfn[x] = ++dcnt;
		if (hson[x] == -1) return;
		dfs2(hson[x], t);
		for (int i = h[x]; i; i = e[i].ne) {
			int u = e[i].t;
			if (u == fa[x] || u == hson[x]) continue;
			dfs2(u, u);
		}
	}
	void add(int x, int y, long long d) {
		int u = x, v = y;
		while(htop[x] != htop[y]) {
			if (dep[htop[x]] < dep[htop[y]]) swap(x, y);
			SEG::add(1, dfn[htop[x]], dfn[x], dis[u] + dis[v] + d);
			x = fa[htop[x]];
		}
		if (x == y) return;
		if (dfn[x] > dfn[y]) swap(x, y);
		SEG::add(1, dfn[x] + 1, dfn[y], dis[u] + dis[v] + d);
	}
}
int main() {
	freopen("path.in", "r", stdin);
	freopen("path.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> m;
	int x, y;
	long long w;
	cnt = 1;
	for (int i = 1; i <= m; i++) {
		cin >> x >> y;
		cin >> w;
		v[x].push_back({y, w});
		v[y].push_back({x, w});
	}
	dij(1);
	for (int i = 2; i <= n; i++) {
		add(i, pre[i]);
		add(pre[i], i);
	}
	TCS::dfs1(1, 0);
	TCS::dfs2(1, 1);
	SEG::bt(1, 1, dcnt);
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < v[i].size(); j++) {
			if ((fa[i] == v[i][j].first || fa[v[i][j].first] == i) && v[i][j].second == (dis[max(i, v[i][j].first)] - dis[min(i, v[i][j].first)])) continue;
			TCS::add(i, v[i][j].first, v[i][j].second);
		}
	}
	for (int i = 2; i <= n; i++) {
		long long val = SEG::ask(1, dfn[i]);
		if (val != 0x3f3f3f3f3f3f3f3f) cout << val - dis[i] << '\n';
		else cout << -1 << '\n';
	}
	return 0;
}
posted @ 2024-10-21 20:16  Peppa_Even_Pig  阅读(21)  评论(2编辑  收藏  举报