11.28 模拟赛

总结

T1 读完题就会了。感觉没什么坑直接写。10min 过大样例。没啥好拍的就不拍了。

T2。感觉不难啊,这种模拟 Kruskal 的题都做一堆了。想。

谔谔正解会不了一点。写个乱搞,看看能不能过大样例。

一开始是没过的,因为少写了一种情况。很久之后意识到改过来发现大样例过了!

然后没对拍。当时真不知道在想啥。

T3 什么玩意,样例解释怎么蹦出个 \(4\)???对称点是啥?????花了一点时间(<5min)发现题意看不懂一点,而且部分分挺少,不管了看 T4。

然而事实上 T3 的题面确实很脑瘫。赛后听 lhl 才终于理解题意。而且全机房就 lhl 读懂了题。。。。。而且 A 了。。。。。。。Orz Orz Orz

T4。不是有结论”与+或=和“吗。那么问题不久变成了从 \([l, r]\) 内选若干数,使得它们的与和为 \(sum / 2\) 吗!

想一想做法。\(nq\) 暴力平凡。\(a_i \le 15\) 平凡。随机……或许也是平凡的。有 \(50\)!!!!!

赶紧写。

然后样例没过。然后发现自己跟个小丑似的”与+或=和“跟着题一点关系没有。

还是只会 \(8\) 分。火大。

T2 做法肯定不是正解,但说不定能骗一堆分。先把保底 \(50\) 写了。

然后我还是小丑,\(u_i \in \{0,1\}\) 写了但是忘拼提交的代码上了。

\(100+40+0+0\)。T4 咋挂了???哦哦哦哦哦哦出题人题面没写集合不能为空。我***

题解

A. 送信卒

注意到 \(k\) 越小最短路一定不会边长。于是二分答案。check 用 Dijkstra。

B. 星际联邦

解法 1(题解做法)

考虑蓝莓(捷克语:Borůvka)算法。


它的思想很类似 Kruskal。Kruskal 一次操作会选择两个连通块合并,而蓝莓算法则是多个。

流程是这样的:

  • 最开始图中有 \(n\) 个连通块。即没有边。
  • 为每个点 \(u\) 找一条边 \((u,v,w)\),其中 \(v\)\(u\) 不在同一个连通块内,且 \(w\) 最小。将这条边称为这个的 最小边
  • 将所有最小边的端点的两个连通块合并。答案加上这些最小边的边权。
  • 若此时剩余连通块数量 \(>1\),回到第二步。否则结束。

【模板】最小生成树 用蓝莓算法实现如下:

int n, m, p[N];
int best[N];		// 最小边
bool st[N];

struct Edge {
	int a, b, c;
}e[N];

int fifa(int x) {
	return x == p[x] ? x : p[x] = fifa(p[x]);
}

void merge(int a, int b) {
	p[fifa(a)] = fifa(b);
}

bool cmp(int a, int b) {
	if (!b) return true;
	if (e[a].c != e[b].c) return e[a].c < e[b].c;
	return a < b;	// 如果边权相等,视作编号小的边更小。
}

void solve() {
	cin >> n >> m;
	
	for (int i = 1; i <= m; ++ i ) {
		cin >> e[i].a >> e[i].b >> e[i].c;
	}
	
	for (int i = 1; i <= n; ++ i ) {
		p[i] = i;
	}
	
	int cnt = 0, res = 0;
	bool flg = true;
	while (flg) {
		flg = false;
		
		memset(best, 0, sizeof best);	
		for (int i = 1; i <= m; ++ i )
			if (!st[i]) {
				int a = fifa(e[i].a), b = fifa(e[i].b);
				if (a == b) continue;
				if (cmp(i, best[a])) best[a] = i;
				if (cmp(i, best[b])) best[b] = i;
			}
		
		for (int i = 1; i <= n; ++ i )
			if (best[i] && !st[best[i]]) {
				flg = true;
				cnt ++ ;
				res += e[best[i]].c;
				st[best[i]] = true;
				merge(e[best[i]].a, e[best[i]].b);
			}
	}
	
	if (cnt == n - 1) cout << res;
	else cout << "orz";
}

复杂度为什么正确?因为一轮操作后图中连通块的数量至少减半。所以复杂度是 \(\mathcal O((n+m)\log n)\)


对于本题,发现一个点 \(i\) 的最小边一定是前缀最大值或后缀最小值。直接维护即可。

但是最小边的定义里写明,两个端点不能在同一个连通块内。于是还需要维护前缀次小值和后缀次大值。这里的 表示不和最值连通块相同的最值。

解法 2(乱搞)

考虑对于每个 \(j\),找到所有 \(i < j\)\(a_i\) 的前 \(M\) 大,和所有 \(i > j\)\(a_i\) 的前 \(M\) 小。这样边的数量是 \(\mathcal O(nM)\) 的。

实测 \(M=18\) 可过。

C. 对称旅行者

考虑期望。

显然一次跳跃后,第 \(i\) 个人会从 \(x_i\) 跳到 \(\frac 12 (2x_i-x_{i-1}+2x_i-x_{i+1}) = x_{i-1}+x_{i+1}-x_i\)

然后 P7962 [NOIP2021] 方差。求 \(x\) 的差分数组 \(\Delta x_i=x_i-x_{i-1}\)

那么第 \(i\) 个人跳一步,相当于交换 \(\Delta x_i,\Delta x_{i+1}\)

为啥?

原本 \(\Delta x_i=x_i-x_{i-1}\)。跳跃后 \(\Delta x'_i = x_{i-1}+x_{i+1}-x_i-x_{i-1}=x_{i+1}-x_i=\Delta x_{i+1}\)

原本 \(\Delta x_{i+1}=x_{i+1}-x_i\)。跳跃后 \(\Delta x'_{i+1}=x_{i+1}-(x_{i-1}+x_{i+1}-x_i)=x_{i-1}-x_i = \Delta x_i\)

也就是说一轮操作 \(a_1 \sim a_m\) 相当于依次执行 \(\operatorname{swap}(\Delta x_{a_1},\Delta x_{a_1+1})\dots \operatorname{swap}(\Delta x_{a_m},\Delta x_{a_m+1})\)。先暴力做一遍,求出 \(p_i\) 表示 \(x'_i = x_{p_i}\)

然后快速幂维护 \(p^k\)

最后前缀和。

posted @ 2024-11-28 20:14  2huk  阅读(6)  评论(0编辑  收藏  举报