A. Single Push
题意
给你两个等长度数组 $a$,$b$,问你是否可以选择或不选数组 $a$ 的一段连续子段,子段每个元素都加上 $k$,使得两数组均相等?
大概思路
题意不难,难的是如何快速写实现。
我们直接对两个数组作差,然后消掉前后缀的连续 $0$,最后再判断剩下的部分是否含 $0$ 即可。
参考代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n), b = a;
for (auto &i : a) std::cin >> i;
for (auto &i : b) std::cin >> i;
for (int i = 0; i < n; i++) {
a[i] -= b[i];
a[i] = -a[i];
if (a[i] < 0) {
std::cout << "NO\n";
return;
}
}
int i = 0;
for (; i < n; i++) {
if (a[i] != 0) break;
}
int j = n - 1;
for (; j >= 0; j--) {
if (a[j] != 0) break;
}
std::set<int> s;
for (int k = i; k <= j; k ++) {
s.insert(a[k]);
}
std::cout << (s.size() <= 1 ? "YES\n" : "NO\n");
}
B. Silly Mistake
题意
给你一个数组,问你是否可以将其分成若干个子数组(不得改变相对位置),使得每个子数组满足如下规则:
- 每种元素只能出现一次
- 绝对值相等的一对元素,正数必须出现在负数前面
- 子数组元素和为 $0$
大概思路
直接模拟即可,考虑贪心,当前已经选取到的子数组元素和为 $0$ 时,立马切割,一定是最优的。如果操作到不能再进行操作时,输出 $-1$ 即可。
参考代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
i64 sum = 0;
for (auto &i : a) (std::cin >> i), sum += i;
if (sum != 0 || a.back() > 0) {
std::cout << -1 << "\n";
return;
}
std::vector<int> ans;
int cnt_ = 1;
int in = 1;
std::set<int> cnt;
cnt.insert(a[0]);
for (int i = 1; i < n; i++) {
cnt_++;
if (a[i] > 0) {//收到正数(入)
if (!cnt.count(a[i])) {//没出现过,加入
cnt.insert(a[i]);
in++;
} else {
std::cout << -1 << "\n";
return;
}
} else {//收到负数(出)
if (!cnt.count(-a[i])) {//没出现过
std::cout << -1 << "\n";
return;
} else {//出现过
in--;
}
}
if (in == 0) {
ans.push_back(cnt_);
cnt_ = 0;
cnt.clear();
}
}
std::cout << ans.size() << "\n";
for (auto &u : ans) std::cout << u << " ";
std::cout << "\n";
}
C. Sweets Eating
题意
津杉给轻音乐俱乐部带来了 $n$ 颗美味的糖果。它们的编号从 $1$ 到 $n$ ,其中 $i$ /th的糖果的糖浓度用整数 $a_i$ 来描述。
Yui 喜欢吃甜食,但出于健康考虑,她每天最多只能吃 $m$ 种甜食。
天数编号从 $1$ 开始(依次为 $1, 2, 3, \ldots$ )。在第 $d$ 天食用 $i$ 种甜食会导致 $(d \cdot a_i)$ 的糖分惩罚,因为随着时间的推移,甜食的糖分会越来越高。一种甜食最多只能吃一次。
糖分惩罚的总和将是每种甜食的糖分惩罚的 总和。
假设小伊选择了 $k$ 种甜食,并按照任意顺序吃完。她能得到的 最少 总糖罚分是多少?
由于小依是一个犹豫不决的女孩,她希望您能回答 $1$ 和 $n$ 之间 $k$ 的每一个值。
大概思路
稍加分析可知,先对原数组排序,然后选前 $k$ 小的元素,从大到小吃,最后的和一定是最小的(排序不等式:反序和 $\le$ 乱序和 $\le$ 正序和)
那么我们要考虑怎么优化这个统计过程,直接暴力统计是 $O(n^2)$ 的,肯定会超时。
考虑当前处理是第 $k + 1$ 个答案,那么 $k$ 的答案肯定是已经处理好的,我们手玩一下样例,看看能不能从前一个答案递推过来。
容易发现,当处理第 $k + 1$ 个答案时,只有 $k + 1,k + 1 - m, k + 1 - 2m,\dots$ 处的贡献发生了变化。那么我们只需要预处理出一个序号公差不为 $1$ 的前缀和,然后每次更新答案的时候,加上 $pre[i]$ 即可。
这个预处理是 $O(n)$ 的,不会超时。
参考代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<i64> a(n);
for (auto &i : a) std::cin >> i;
std::sort(a.begin(), a.end());
std::vector<i64> pre(n);
for (int i = 0; i < m; i++) {
pre[i] = a[i];
for (int j = i + m; j < n; j += m) {
pre[j] += a[j] + pre[j - m];
}
}
i64 ans = 0;
for (int i = 0; i < n; i++) {
ans += pre[i];
std::cout << ans << " ";
}
std::cout << "\n";
}
D. Harmonious Graph
题意
给你一个有 $n$ 个节点的无向图,让你连上最少的边数,使得“若任意两个节点 $l,r(l < r)$之间可达,那么 $l,k(l < k < r)$ 之间也是可达的”成立。
大概思路
其实题意可以转化为,给你若干个联通块,问你最少合并几次,可以让每个联通块内部的元素均是连续的(即,如果一个联通块有 $1$ 和 $3$,那么这个联通块一定含有 $2$)。
考虑从小到大遍历每个元素,同时维护一个“截至到第 $i$ 个元素,访问过的联通块的内部元素最大值” $MAX$。若当前访问到的元素 $i$ 与 $MAX$ 不在一个并查集里,且 $i < MAX$ 那么必须连边。
遍历一遍,统计连边次数即可。
参考代码
const int N=200005;
int F[N], mx[N];//维护并查集元素最大最小值
int find(int x) {
if(F[x]!=x) {
int t=F[x];//暂存其亲戚
F[x]=find(F[x]);
mx[x] = std::max(mx[x], mx[t]);
}
return F[x];
}
void join(int x,int y){
int Fx=find(x),Fy=find(y);
if(Fx!=Fy){
F[Fx]=Fy;
mx[Fy] = std::max(mx[Fy], mx[Fx]);
}
}
void solve() {
int n, m;
std::cin >> n >> m;
for (int i = 1; i <= n; i++) F[i] = mx[i] = i;
while (m--) {
int x, y;
std::cin >> x >> y;
join(x, y);
}
int mxx = mx[1];
int ans = 0;
for (int i = 1; i <= n; i++) {
if (find(mxx) != find(i) && i < mxx) {
ans++;
join(mxx, i);
}
mxx = std::max(mxx, mx[i]);
}
std::cout << ans;
}