ABC390 - 魔怔记
复盘
A,把所有可能的答案列举出来,最后字符串判等即可……
B,没什么难度,码完交上去 WA 3 个点。
不知道是不是精度问题,开了 long double,WA 1 个点。
调调调,发现 eps 设大了,改成 \(10^{-12}\) 过了。
C 算什么东西,随便写写就过了。
D,一眼看到数据范围是个搜索,写出来 T 了,调了一下发现常数大了一点,卡卡常过了。
E,本来打了一个无敌大背包,然后柿子推错了,改成了二分答案套背包,改改就一遍过了。
F,一眼扫描线,并想都没想就上线段树(线段树题做魔怔了导致的),然后调到了结束遗憾离场。
后面想了想 F,发现好像没必要上线段树(嗯又不魔怔了),改了下不仅代码长度少 \(40\) 行还少了只 \(\log\)。
题解
A
堂题,不说了。
B
堂题,但是注意精度。
C
堂题,不说了。
D
dfs + 剪枝。
首先,暴力搜索 \(a\) 中哪两个数相加显然不够优秀,会 T 飞,我们考虑换一种搜索方式。
对于每一个数 \(a_i\),找到一个 \(b_j\),将 \(a_i\) 加到 \(b_j\) 上,最后统计 \(b\) 的异或和也可以达到与题目相符的同样的结果(\(b\) 数组可以看作我们新定义的初始为空的数组)。
但是他仍然 T 飞了。
冷静分析,发现每一次搜索没必要搜 \(n\) 个位置,只需要搜到 \(b\) 中第一个为 \(0\) 的位置即可,这样也可以做到不重不漏。
不过同样的优化我们也需要放在统计 \(b\) 的异或和的循环里。
卡卡常就能过了。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;
int _test_ = 1;
const int N = 14;
int n, a[N], b[N], ans;
unordered_map<int, int> mp; // 补药用 map,会 T
void dfs(int u) {
if (u == n + 1) {
int g = 0;
for (int i = 1; i <= n; i++) {
if (b[i] == 0) break; // 如果当前位置为 0 直接跳出循环
g ^= b[i];
}
if (mp[g] == 0) // 又多了一个答案
ans++, mp[g] = 1;
}
bool zero = false;
for (int i = 1; i <= n; i++) {
zero = b[i] == 0;
b[i] += a[u];
dfs(u + 1);
b[i] -= a[u];
if (zero) break; // 如果当前位置为 0 直接跳出循环
}
}
void init() {}
void clear() {}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
dfs(1);
cout << ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
// cin >> _test_;
init();
while (_test_--) {
clear();
solve();
}
return 0;
}
E
二分 + 背包
我们先不考虑哪一种维生素摄入量最少,只二分摄入量的最小值,其他的事情交给 check。
在 check 里,对每一种维生素食品都做一次背包。
接着我们找到最小的产生的卡路里值,使得该种维生素摄取量 \(\gt mid\)(如果没有显然就不满足条件),求和统计一下。
因为每种食物都只会涉及到一种维生素,所以每种食品只会被考虑一遍,那么最后统计到的和就是使得每种维生素摄取量的最小值 \(\gt mid\) 的最小卡路里消耗量,与 \(X\) 比较一下即可。
注意:
- 二分的起点是 \(l = 0, r = 10^{18}\),不要弄错了。
- 背包是 01 背包,不要打成别的背包了。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;
int _test_ = 1;
const int N = 5005, X = 5005;
struct node {
int A[3];
} a[N];
int n, t;
int dp[N];
bool check(int x) {
int ans = 0;
for (int op = 1; op <= 3; op++) {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++) {
if (a[i].A[0] != op) continue;
for (int j = N - 5; j >= a[i].A[2]; j--) {
dp[j] = max(dp[j], dp[j - a[i].A[2]] + a[i].A[1]);
}
}
if (dp[N - 5] < x) return false;
for (int i = 1; i <= N - 5; i++) {
if (dp[i] < x) continue;
ans += i;
break;
}
}
return ans <= t;
}
void init() {}
void clear() {}
void solve() {
cin >> n >> t;
for (int i = 1; i <= n; i++) {
cin >> a[i].A[0] >> a[i].A[1] >> a[i].A[2];
}
int l = 0, r = 1e18, ans = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
// cin >> _test_;
init();
while (_test_--) {
clear();
solve();
}
return 0;
}
F
枚举 \(R\),找到所有 \(L\) 的结果之和。
记一个 \(l\) 数组,\(l_{a_i}\) 就代表上一次 \(a_i\) 出现的下标。
会影响到 \(a_R\) 的一共只有三个数,\(a_R - 1\)、\(a_R\)、\(a_R + 1\),分别计算以一下。
值得注意的是,如果 \(a_R - 1\) 和 \(a_R + 1\) 都存在但是 \(a_R\) 不存在,添加上 \(a_R\) 之后区间切割就可以少切一刀了。
维护维护就行了。
#include <bits/stdc++.h>
#define int long long
#define pii pair<int, int>
#define FRE(x) freopen(x ".in", "r", stdin), freopen(x ".out", "w", stdout)
#define ALL(x) x.begin(), x.end()
using namespace std;
int _test_ = 1;
const int N = 3e5 + 5;
int n, a[N], lst[N];
void init() {}
void clear() {}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int ans = 0, now = 0;
for (int i = 1; i <= n; i++) {
int _r = lst[a[i] - 1], r = lst[a[i]], r_ = lst[a[i] + 1];
now += i - r;
if (_r > r) now += r - _r;
if (r_ > r) now += r - r_;
lst[a[i]] = i;
ans += now;
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
// cin >> _test_;
init();
while (_test_--) {
clear();
solve();
}
return 0;
}

浙公网安备 33010602011771号