20220725模拟赛题解

20220725模拟赛(二分专题)

T1 自动刷题机

题目描述

曾经发明了信号增幅仪的发明家 SHTSC 又公开了他的新发明:自动刷题机——一种可以自动 AC 题目的神秘装置。

自动刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写程序。每秒,自动刷题机的代码生成模块会有两种可能的结果:

  • 写了 \(x\) 行代码

  • 心情不好,删掉了之前写的 \(y\) 行代码。(如果 \(y\) 大于当前代码长度则相当于全部删除。)

对于一个 OJ,存在某个固定的正整数长度 \(n\),一旦自动刷题机在某秒结束时积累了大于等于 \(n\) 行的代码,它就会自动提交并 AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC 在某个 OJ 上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个 OJ\(n\) 究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 \(k\) 道题,希望你计算 \(n\) 可能的最小值和最大值。(可能无解,输出 -1。)

输入格式

第一行两个整数 \(l, k\) 表示刷题机的日志一共有 \(l\) 行,一共了切了 \(k\) 题。

接下来 \(l0\) 行,每行一个整数 \(x_i\),依次表示每条日志。若 \(x_i \geq 0\),则表示写了 \(x_i\)行代码,若 \(x_i \lt 0\),则表示删除了 \(-x_i\) 行代码。

输出格式

输出一行两个整数,分别表示 \(n\) 可能的最小值和最大值。

如果这样的 \(n\) 不存在,请输出一行一个整数 -1

解题思路

考虑二分答案,直接二分 \(n\)

可以每次 \(\mathcal{O(n)}\) 完成 check

即每次 check 时的 AC 的代码数量为 \(k'\)

只有当二分到 \(k' = k\) 时,才可以将 minmax 更新。

对于 -1 的情况,即两次二分后 minmax 至少有一个没有被更新过。

  1. min
    \(k' \leq k\) 时,说明 \(n\) 过大,将右端点左移。
    \(k' \ge k\) 时,说明 \(n\) 过小,将左端点右移。
  2. max
    \(k' \le k\) 时,说明 \(n\) 过大,将右端点左移。
    \(k' \geq k\) 时,说明 \(n\) 过小,将左端点右移。

代码

#include <bits/stdc++.h>
#define int long long
const int N = 1e5 + 5, inf = 1e14; int L, k, max, min, a[N];
int check(int x) {
	int now = 0, ans = 0;
	for (int i = 1; i <= L; ++i) {
		now += a[i];
		if (now < 0) now = 0;
		if (now >= x) ++ ans, now = 0;
	} return ans;
}
signed main() {
	scanf("%lld%lld", &L, &k);	
	for (int i = 1; i <= L; ++i) scanf("%lld", a + i);
	int l = 1, r = inf; max = -inf, min = inf;
	while (l <= r) {
		int mid = l + r >> 1;
		int c = check(mid);
		if (c <= k) r = mid - 1, min = c == k ? std::min(min, mid) : min;
		else l = mid + 1;
	} l = 1, r = inf;
	while (l <= r) {
		int mid = l + r >> 1;
		int c = check(mid);
		if (c >= k) l = mid + 1, max = c == k ? std::max(max, mid) : max;
		else r = mid - 1;
	}
	if (max == -inf || min == inf) puts("-1");
	else printf("%lld %lld\n", min, max);
}

信号强度

题目描述

\(n\) 个通信节点,在每个节点都设置一个信号强度为 \(W\) 的收发器。有 \(m\) 对通信节点之间可能直接通信,描述为:给定节点 \(u_i\),节点 \(v_i\) 和通信所需的最低强度 \(w_i\),只要收发器的信号强度 \(W \geq w_i\),则节点 \(u\) 和节点 \(v\) 之间就可以直接通信。两个节点可以通过若干次直接通信转发信息来进行间接通信。

现在你需要让 \(n\) 个通信节点中的若干组节点在组内可以互相通信(直接通信和间接通信皆可),同时使得收发器所需的信号强度尽量低。除了设置收发器之外,你还能铺设一条光纤。这条光纤可以铺设在原先需要信号强度 \(W \geq w_i\) 才能直接通信的两个节点之间。无论收发器的信号强度 \(W\) 是多少,这条光纤都可以使得这两个节点进行直接通信。

输入格式

第一行两个整数 \(n\)\(m\),表示通信节点个数和可以直接通信的节点对数。

以下 \(n\) 行,每行一个小写字母 \((a-z)\) 表示该节点所属的组。若为 '#',则表示该节点不属于任何一个组。节点从 \(0\) 开始编号。

接下来 \(m\) 行,每行三个整数,表示 \(u_i, v_i, w_i\)。节点编号为 \(0\)\(n-1\)

输出格式

一个数,表示让组内节点可以互相通信所需的最小信号强度 \(W\)

解题思路

考虑二分答案。

每次 check 首先将 \(w_i \leq W\) 的边加入,用并查集维护联通性。

当一个组内出现两个联通块时,将两个联通块标记一下(这边我用的是map)。

然后去扫 \(w_i \ge W\) 的边,发现有一条边联通两个不同的联通块,且该联通块被标记时,这条边肯定为光纤(注意只能有一条光纤)。

此时我们只需要再对每个联通块扫一遍,若此时一个组内存在两个及以上的联通块,则不合法,反之合法。

代码

#include <bits/stdc++.h>
using pii = std::pair<int, int>;
const int N = 1e5 + 5, M = 4e5 + 5, inf = 1e9 + 7;
int n, m, cnt = 1, l, r, head[N], fa[N]; char ch;
std::vector<int> group[26]; std::map<pii, int> map;
struct edge{int from, to, link, next;} e[M << 1];
void add(int x, int y, int z) {e[++ cnt] = {x, y, z, head[x]}; head[x] = cnt;}
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
bool check(int x) {
	for (int i = 1; i <= n; ++i) fa[i] = i;
	for (int i = 1; i <= m; ++i) {
		if (e[i << 1].link > x) continue;
		int x = e[i << 1].from, y = e[i << 1].to;
		x = find(x), y = find(y);
		if (x != y) fa[x] = y;
	}
	for (int i = 0; i < 26; ++i) {
		if (group[i].empty()) continue;
		int t = find(group[i][0]);
		for (auto p : group[i]) {
			int j = find(p);
			if (t ^ j) {
				if (j > t) std::swap(j, t);
				map[pii(j, t)] = true; 
			}
		}
	}
	bool flag = true;
	for (int i = 1; i <= m; ++i) {
		if (e[i << 1].link <= x) continue;
		int x = e[i << 1].from, y = e[i << 1].to;
		x = find(x), y = find(y); if (x > y) std::swap(x, y);
		if ((x ^ y) && map[pii(x, y)] && flag) fa[x] = y, flag = false;
	}
	for (int i = 0; i < 26; ++i) {
		if (group[i].empty()) continue;
		int t = find(group[i][0]);
		for (auto p : group[i]) if (t ^ find(p)) return false; 
	} return true;
}
signed main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		std::cin >> ch;
		if (ch != '#') group[ch - 'a'].emplace_back(i);
	}
	for (int i = 1, u, v, w; i <= m; ++i) {
		scanf("%d%d%d", &u, &v, &w); ++ u, ++ v;
		add(u, v, w); add(v, u, w); r = std::max(r, w);
	}
	while (l < r) {
		int mid = l + r >> 1;	
		if (check(mid)) r = mid;
		else l = mid + 1;
	} return printf("%d\n", l), 0;
}

另外一个思路

考场上考虑了一个贪心。

先不考虑光纤,二分出来的 W 一定是某条边的权值。

那么这条边一定是光纤。

将该边的权值赋值为 0,再跑一遍二分。

这个思路的时间复杂度约为 \(\mathcal{O(n \log W)}\)

但是由于常数太大被卡了\qd。

代码2

#include <bits/stdc++.h>
const int N = 1e5 + 5, M = 4e5 + 5, inf = 1e9 + 7;
int n, m, cnt = 1, l, r, head[N]; bool vis[N]; char ch;
std::vector<int> group[26];
struct edge{int from, to, link, next;} e[M << 1];
void add(int x, int y, int z) {e[++ cnt] = {x, y, z, head[x]}; head[x] = cnt;}
void solve(int x, int limit) {
	static std::queue<int> q; q.emplace(x);
	while (q.size()) {
		int t = q.front(); q.pop();
		if (vis[t]) continue; vis[t] = true;
		for (int i = head[t]; i; i = e[i].next) {
			if (e[i].link > limit) continue;
			q.emplace(e[i].to);
		}
	} return void();
}
bool check(int x) {
	for (int i = 0; i < 26; ++i) {
		if (group[i].empty()) continue;
		for (int j = 1; j <= n; ++j) vis[j] = false;
		solve(group[i][0], x);
		for (auto p : group[i]) if (!vis[p]) return false;
	} return true;
}
signed main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i) {
		std::cin >> ch;
		if (ch != '#') group[ch - 'a'].emplace_back(i);
	}
	for (int i = 1, u, v, w; i <= m; ++i) {
		scanf("%d%d%d", &u, &v, &w); ++ u, ++ v;
		add(u, v, w); add(v, u, w); r = std::max(r, w);
	}
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	for (int i = 1; i <= 2 * m; i += 2)
		if (e[i].link == l) {e[i].link = e[i ^ 1].link = 0; break;}
	l = 0, r = inf;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	} return printf("%d\n", l), 0;
}

\(\texttt{DeaphetS}\) 的序列

题目描述

\(\texttt{DeaphetS}\) 有一个长度为 \(n\) 的序列,序列中第 \(i\) 个数的值为 \(a_i\)

对于该序列的一个连续子序列 \(a_l,a_{l+1},\cdots ,a_r\) 来说,其带给 \(\texttt{DeaphetS}\) 的愉悦度为 \(a_l\bmod a_{l+1}\bmod \cdots \bmod a_r\)。其中 \(a\bmod b\) 表示 \(a\) 除以 \(b\) 后的余数。

现在 \(\texttt{DeaphetS}\) 想知道,这个序列的所有连续子序列能给他带来的愉悦度的和是多少。

输入格式

第一行一个正整数 \(n\),表示序列的长度。

接下来一行 \(n\) 个正整数,第 \(i\) 个数为 \(a_i\)

输出格式

一行一个整数,表示这个序列的所有连续子序列能给他带来的愉悦度的和。

解题思路

需要注意到,每次对一个数取模后,值要么不变,要么至少减少一半,所以对于确定的 \(a_i\),答案的值只有 \(\log_{2}{a_i}\) 段。

那么只需要快速找到每一段的分界点即可,对于当前的值 \(x\) 以及当前的左端点 \(l\),我们需要找到最小的 \(r \geq l\) 使得 \(a_r \leq x\)。使用各种数据结构均可维护。

我们同样可以维护一个堆,通过当前点维护之前点取模的值,复杂度同理。

代码

#include <bits/stdc++.h>
#define int long long
const int N = 3e5 + 5, M = 5e3 + 5; int n, ans, a[N];
std::priority_queue<int> q;
signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i) scanf("%lld", a + i);
	int now = 0;
	for (int i = 1, t; i <= n; ++i) {
		while (q.size() && (t = q.top()) >= a[i]) {
			q.pop();
			now -= t;
			now += t % a[i];
			q.push(t % a[i]);
		}
		now += a[i];
		q.push(a[i]);
		ans += now;
	} return printf("%lld\n", ans), 0;
}

项链

题目描述

\(\texttt{DeaphetS}\)\(\texttt{Macesuted}\) 都有一串环形项链,上面串着 \(n\) 个珠子,每个珠子都有一个颜色。

\(\texttt{DeaphetS}\) 觉得 \(\texttt{Macesuted}\) 的项链很好看,计划将自己的项链染成和 \(\texttt{Macesuted}\) 相同的项链,\(\texttt{DeaphetS}\) 每次可以将几个连续的珠子染成某一个颜色,但是每染一次都要消耗一定的成本,而 \(\texttt{DeaphetS}\) 不知道最少需要染几次,于是放弃了这个计划。

终于有一天,\(\texttt{DeaphetS}\)\(\texttt{Macesuted}\) 的项链都断开了,由环形变成了链状,\(\texttt{DeaphetS}\) 依然可以将一段连续的珠子染成某一个颜色,可是她想来想去还是不知道最少需要染几次,这次她不想放弃,只好求助于你。

(要考虑实际情况中的镜像问题,即 12344321 被认为是相同的项链)

输入格式

输入文件的第一行包含一个正整数 \(n\),表示一条项链上有 \(n\) 个珠子。

第二行包含 \(n\) 个正整数 \(a_1, a_2, \cdots, a_n\),依次表示 \(\texttt{DeaphetS}\) 项链上 \(n\) 个珠子的颜色。

第三行包含 \(n\) 个正整数 \(b_1, b_2, \cdots, b_n\),依次表示 \(\texttt{Macesuted}\) 项链上 \(n\) 个珠子的颜色。

输出格式

输出一个正整数,表示将 \(\texttt{DeaphetS}\) 的项链染成 \(\texttt{Macesuted}\) 的项链所需的最少次数。

解题思路

先考虑 \(a\)\(b\) 互不相同,只时候可以设计状态 \(f_{i, j}\) 表示将 \(a_i, a_{i+1}, \cdots, a_j\) 染为 \(b_i, b_{i+1}, \cdots, b_j\) 的最小代价。

如果是单点染色,那么容易得出 \(f_{i, j} = min(f_{i, k} + f_{k + 1, j})\)

如果是区间染色,即左右端点颜色相同,可以先花费 \(1\) 的代价,将整个区间染色,再考虑区间内部,此时 \(f_{i, j} = f_{i, j - 1}\) 或者写为 -- f[i][j]

\(g[i]\) 表示将长度为 \(i\) 的前缀染成对应颜色的最小花费,那么就有转移 \(g_i = min(g_{j-1} + f_{j, i})\),当 \(a_i = b_i\) 时,不做考虑,即 \(g_i = g_{i-1}\)

代码

#include <bits/stdc++.h>
const int N = 5e2 + 5; int n, ans = N, a[N], b[N], g[N], f[N][N];
int solve(int *a, int *b) {
	std::memset(f, 0x3f, sizeof f);
	for (int i = 1; i <= n; ++i) f[i][i] = 1;
	for (int i = 2; i <= n; ++i)
		for (int l = 1; l + i - 1 <= n; ++l) {
			int r = l + i - 1;
			for (int j = l; j < r; ++j) f[l][r] = std::min(f[l][r], f[l][j] + f[j + 1][r]);
			if (b[l] == b[r]) f[l][r] = f[l][r - 1];
		}
	for (int i = 1; i <= n; ++i) {
		g[i] = f[1][i];
		for (int j = 1; j < i; ++j) g[i] = std::min(g[i], g[j] + f[j + 1][i]);
		if (a[i] == b[i]) g[i] = g[i - 1];
	} return g[n];
}
signed main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", a + i);
	for (int i = 1; i <= n; ++i) scanf("%d", b + i);
	ans = std::min(ans, solve(a, b));
	std::reverse(b + 1, b + n + 1);
	ans = std::min(ans, solve(a, b));
	return printf("%d\n", ans), 0;
}
posted @ 2022-07-25 16:34  xxcxu  阅读(76)  评论(0编辑  收藏  举报