写在前面
更好的阅读体验:https://www.cnblogs.com/luckyblock/p/18785878
比赛地址:https://ac.nowcoder.com/acm/contest/103957。
呃呃太唐了。
A
签到。
#include <bits/stdc++.h>
#define LL long long
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int a, b, w; std::cin >> a >> b >> w;
if (a == w || b == w || (a + b == w) || (a + w == b) || (b + w == a)) {
std::cout << "Yes" << "\n";
} else {
std::cout << "No" << "\n";
}
return 0;
}
B
签到,枚举。
#include <bits/stdc++.h>
#define LL long long
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int n; std::cin >> n;
std::vector<int> a(n + 2);
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
int ans = -1;
for (int i = 2; i <= n - 1; ++ i) {
if (a[i] > a[i - 1] && a[i] > a[i + 1]) {
ans = std::max(ans, a[i] - (a[i - 1] + a[i + 1]) / 2);
}
}
std::cout << ans << "\n";
return 0;
}
C
特判。
保证最终剩下的所有桶中都不触发消除,则最终剩下的球数量的上界显然为 。
当 时若有解,则一种一定可行的操作方案是:不断往第一个桶里放球,直至恰好消除
个球,然后将剩余的球平均放到所有桶里,此时一定不会触发消除。则有解当且仅当:
#include <bits/stdc++.h>
#define LL long long
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T; std::cin >> T;
while (T --) {
LL n, m, k, q;
std::cin >> n >> m >> k >> q;
int ans = (q <= m * (k - 1) && (n - q) % k == 0);
std::cout << (ans ? "Yes" : "No") << "\n";
}
return 0;
}
D
树,枚举。
记节点 的度数为
。容易发现对于一棵以
为根的有根树,根节点
的孩子数为
,其余所有点
的孩子数为
。
于是考虑预处理 的前后缀最大值
和
,然后考虑枚举根节点
,此时树的叉数
即为:
于是在所有的答案中按题意取最小值即可。
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
int n, into[kN], pre[kN], suf[kN];
std::vector<int> edge[kN];
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> n;
for (int i = 1; i < n; ++ i) {
int u, v; std::cin >> u >> v;
edge[u].push_back(v), edge[v].push_back(u);
++ into[u], ++ into[v];
}
for (int i = 1; i <= n; ++ i) pre[i] = std::max(pre[i - 1], into[i]);
for (int i = n; i; -- i) suf[i] = std::max(suf[i + 1], into[i]);
int ans1 = n, ans2 = 0;
for (int i = 1; i <= n; ++ i) {
int v = std::max(pre[i - 1] - 1, std::max(into[i], suf[i + 1] - 1));
if (v < ans1) ans1 = v, ans2 = i;
}
std::cout << ans1 << " " << ans2 << "\n";
return 0;
}
E
枚举,DP。
众所周知有:
发现数据范围很小,于是考虑每次询问时大力枚举被询问数 的所有因数
,则题目所求即为能否使用给定数列凑出两个数
和
。
先考虑一个暴力 DP,记 使用数列前
个数,能否凑出行的和为
,列的和为
。初始化
,转移时大力枚举每个数
放到行/列,或者舍弃:
上述状态可以在 时间复杂度内预处理出来。则对于每次询问都大力枚举被询问书的因数
,有解当且仅当存在
,使得
。
这个时候写完代码准备交了,一看下面我草居然还要输出方案!?怎么搞?!
于是考虑改造一下 DP 状态,使之能够记录当前这步转移进行了什么操作即可。记 表示若
,当前这步转移对
进行的操作为:舍弃/加到了行上/加到了列上。若
则
。在进行上述枚举转移
时顺便维护
即可。
每次询问时,若枚举到了一个合法的 满足
,则考虑倒序枚举
,并根据记录的
还原每一步的操作即可,总时间复杂度
。
实现时可以合并两个数组 和
,具体实现详见代码。
注意:以下代码为根据上述暴力 DP 直接实现的,并不能通过 hard version 的 F 题。
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
const int kM = 110;
int n, m, a[kN];
int from[kN][kM][kM];
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
from[0][0][0] = -1;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= 100; ++ j) {
for (int k = 0; k <= 100; ++ k) {
if (from[i - 1][j][k]) from[i][j][k] = -1;
}
}
for (int j = a[i]; j <= 100; ++ j) {
for (int k = 0; k <= 100; ++ k) {
if (!from[i][j][k] && from[i - 1][j - a[i]][k]) from[i][j][k] = 1;
}
}
for (int j = 0; j <= 100; ++ j) {
for (int k = a[i]; k <= 100; ++ k) {
if (!from[i][j][k] && from[i - 1][j][k - a[i]]) from[i][j][k] = 2;
}
}
}
while (m --) {
int x, ans = 0; std::cin >> x;
for (int d = 1; d * d <= x; ++ d) {
if (x % d) continue;
if (from[n][d][x / d]) {
ans = d;
break;
} else if (from[n][x / d][d]) {
ans = x / d;
break;
}
}
std::cout << (ans ? "Yes" : "No") << "\n";
if (!ans) continue;
int suma = ans, sumb = x / ans;
std::vector<int> ans1, ans2;
for (int i = n; i; -- i) {
if (from[i][suma][sumb] == -1) continue;
if (from[i][suma][sumb] == 1) ans1.push_back(a[i]), suma -= a[i];
if (from[i][suma][sumb] == 2) ans2.push_back(a[i]), sumb -= a[i];
}
std::cout << ans1.size() << " " << ans2.size() << "\n";
for (auto v: ans1) std::cout << v << " ";
std::cout << "\n";
for (auto v: ans2) std::cout << v << " ";
std::cout << "\n";
}
return 0;
}
F
DP,减去无用状态、无用转移的 DP 优化。
请首先阅读上一题 easy version 的题解。
发现此时直接套用上述状态直接做空间时间都会爆掉。但是发现数据范围并没有增大很多,于是考虑能否优化。
发现上述状态中,行和列实际上是没有区别的,状态 和状态
本质上是相同的,则实际上存在大量的无用状态和无用转移。
由于询问满足 ,根据小学数学知识可以发现,若
有解,即存在
,则一定有:
。于是考虑钦定在上述状态
和
中,
不大于
,则对应的
一定不大于
(即钦定构造方案时保证行的和
不大于列的和
)。
此时状态 空间复杂度变为
级别,空间复杂度上没问题了。转移时钦定枚举
时不大于
,
不大于
,则由调和级数可知,对于每个
转移次数为:
然后就能在 的时间复杂度内处理出所有状态了。询问时保证枚举的因数
,然后套用 E 的做法即可。总时间复杂度
#include <bits/stdc++.h>
#define LL long long
const int kN = 510;
const int kM = 110;
int n, m, a[kN];
int from[kN][kM][kM * kM];
int main() {
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
from[0][0][0] = -1;
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= 100; ++ j) {
for (int k = 0; k <= 100 * 100 / std::max(1, j); ++ k) {
if (from[i - 1][j][k]) from[i][j][k] = -1;
}
}
for (int j = a[i]; j <= 100; ++ j) {
for (int k = 0; k <= 100 * 100 / std::max(1, j); ++ k) {
if (!from[i][j][k] && from[i - 1][j - a[i]][k]) from[i][j][k] = 1;
}
}
for (int j = 0; j <= 100; ++ j) {
for (int k = a[i]; k <= 100 * 100 / std::max(1, j); ++ k) {
if (!from[i][j][k] && from[i - 1][j][k - a[i]]) from[i][j][k] = 2;
}
}
}
while (m --) {
int x, ans = 0; std::cin >> x;
for (int d = 1; d * d <= x; ++ d) {
if (x % d) continue;
if (from[n][d][x / d]) {
ans = d;
break;
}
}
std::cout << (ans ? "Yes" : "No") << "\n";
if (!ans) continue;
int suma = ans, sumb = x / ans;
std::vector<int> ans1, ans2;
for (int i = n; i; -- i) {
if (from[i][suma][sumb] == -1) continue;
if (from[i][suma][sumb] == 1) ans1.push_back(a[i]), suma -= a[i];
if (from[i][suma][sumb] == 2) ans2.push_back(a[i]), sumb -= a[i];
}
std::cout << ans1.size() << " " << ans2.size() << "\n";
for (auto v: ans1) std::cout << v << " ";
std::cout << "\n";
for (auto v: ans2) std::cout << v << " ";
std::cout << "\n";
}
return 0;
}
写在最后
学到了什么:
- F:减去无用状态、无用转移的 DP 优化。