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\) 时,才可以将 min
和 max
更新。
对于 -1
的情况,即两次二分后 min
和 max
至少有一个没有被更新过。
- 求
min
当 \(k' \leq k\) 时,说明 \(n\) 过大,将右端点左移。
当 \(k' \ge k\) 时,说明 \(n\) 过小,将左端点右移。 - 求
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}\) 依然可以将一段连续的珠子染成某一个颜色,可是她想来想去还是不知道最少需要染几次,这次她不想放弃,只好求助于你。
(要考虑实际情况中的镜像问题,即 1234
和 4321
被认为是相同的项链)
输入格式
输入文件的第一行包含一个正整数 \(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;
}
本文来自博客园,作者:xxcxu,转载请注明原文链接:https://www.cnblogs.com/Maraschino/p/16517854.html