模拟赛总结
导读
由于好渴鹅过于智慧,因此这段时间一直在掉大分,因此写篇博客压压惊。
题目
救生员(lifeguards)
这题灰常的简单,每一个线段被取消之后只会丢失这个线段与其他线段不重复的部分,因此排完序我们就可以直接贪心。写一个函数求出线段最小的自己所有的部分,注意要对 \(0\) 取 \(\max\)。
#include <iostream>
#include <algorithm>
using namespace std;
using ll = long long;
const ll kMaxN = 1e6 + 5;
struct Node {
ll l, r;
} a[kMaxN];
ll n, p, s, ans;
ll solve() {
ll s = 1e9;
for (ll i = 1; i <= n; i++) {
if (a[i].r <= a[i - 1].r || a[i + 1].l <= a[i - 1].r) {
return 0;
}
s = min(s, min(a[i + 1].l, a[i].r) - max(a[i - 1].r, a[i].l));
}
return s;
}
int main() {
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i].l >> a[i].r;
}
sort(a + 1, a + n + 1, [](const Node &a, const Node &b) {
return a.l < b.l || (a.l == b.l && a.r < b.r);
});
a[0].r = a[1].l, a[n + 1] = {a[n].r, (ll)1e9};
p = a[1].l;
for (ll i = 1; i <= n; i++) {
s += max(0LL, a[i].r - max(a[i].l, p));
p = max(p, a[i].r);
}
cout << s - solve();
return 0;
}
牛奶桶(pails)
直接设 \(dp_i\)(\(dp_i\in 0,1\)) 表示是否可以使用 \(i\) 的牛奶,状态转移方程:
#include <iostream>
using namespace std;
int x, y, m;
int main() {
cin >> x >> y >> m;
dp[0] = 1;
for (int i = 1; i <= n; i++) {
i >= x && (dp[i] |= dp[i - x]);
i >= y && (dp[i] |= dp[i - y]);
}
cout << (dp[m] ? "Yes" : "No") << '\n';
return 0;
}
奶牛租赁(rental)
我们知道让产奶量最少的奶牛卖出去是可以获得更多钱的,那么我们先将奶牛排序,排完序之后就变成了枚举中间点,一半卖出、一半卖奶的问题了。我们可以使用前缀和以及后缀和进行维护。一定要枚举 \(0\) 啊啊啊啊!
永远怀念好渴鹅的 90 分
#include <iostream>
#include <algorithm>
using namespace std;
using ll = long long;
const ll kMaxN = 1e5 + 5;
struct Node {
ll q, p;
} d[kMaxN];
ll a[kMaxN], v[kMaxN], sv[kMaxN], sd[kMaxN], n, m, r, ans;
int main() {
cin >> n >> m >> r;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
for (ll i = 1; i <= m; i++) {
cin >> d[i].q >> d[i].p;
}
sort(d + 1, d + m + 1, [](const Node &a, const Node &b) {
return a.p < b.p;
});
for (ll i = 1; i <= r; i++) {
cin >> v[i];
}
sort(v + 1, v + r + 1, greater<ll>());
// 预处理前缀和
for (ll i = 1; i <= n; i++) {
sv[i] = sv[i - 1] + (i <= r ? v[i] : 0);
}
// 预处理后缀和
ll t = m;
for (ll i = n; i >= 1; i--) {
sd[i] = sd[i + 1];
while (t >= 1) {
if (a[i] >= d[t].q) {
sd[i] += d[t].q * d[t].p, a[i] -= d[t].q, t--;
} else {
sd[i] += a[i] * d[t].p, d[t].q -= a[i], a[i] = 0;
break;
}
}
}
// 处理每头奶牛
for (ll i = 0; i <= n; i++) {
ans = max(ans, sv[i] + (sd[i + 1] - sd[n + 1]));
}
cout << ans << '\n';
return 0;
}
MooTube(mootube)
一道大水题。我先我们看到这个 \(1\le N,Q\le 5000\),然后进行 dfs 暴力。我们进行一个小小的剪枝优化:当前如果已经无法推荐了,那么直接 return,这样子就可以暴力卡过。
#include <iostream>
#include <utility>
#include <vector>
#define add(u, v, w) e[u].push_back({v, w})
using namespace std;
using ll = long long;
const ll kMaxN = 5005;
ll n, q, k, u, ans;
vector<pair<ll, ll>> e[kMaxN];
void dfs(ll x, ll f, ll s) {
if (s < k) {
return;
}
ans++;
for (auto i: e[x]) {
if (i.first != f) {
dfs(i.first, x, min(s, i.second));
}
}
}
int main() {
cin >> n >> q;
for (ll i = 1, u, v, w; i < n; i++) {
cin >> u >> v >> w;
add(u, v, w), add(v, u, w);
}
for (ll i = 1; i <= q; i++) {
cin >> k >> u, ans = 0;
dfs(u, 0, 1e9);
cout << ans - 1 << '\n';
}
return 0;
}
密码代码(scode)
我们可以从结束状态直接进行大暴力分治。设 \(f(s)\) 表示当前的字符串 \(s\) 有多少种方案,如果还没有求解,那么就进行递归求解,否则就直接调用之前的记忆。其实不用记忆化也可以卡过的。。。
#include <iostream>
#include <string>
#include <map>
#include <set>
using namespace std;
const int kMod = 2014;
string s;
map<string, int> dp;
int dfs(string s) {
if (s.size() <= 1) {
return 0;
}
if (dp.count(s)) {
return dp[s];
}
int ans = 1;
for (int i = 0; i < s.size() - 1; i++) {
string a = s.substr(0, i + 1);
string b = s.substr(i + 1, s.size() - i - 1);
if (a.size() > b.size()) {
if (a.substr(0, b.size()) == b) {
ans += dfs(a), ans %= kMod;
}
if (a.substr(a.size() - b.size(), b.size()) == b) {
ans += dfs(a), ans %= kMod;
}
}
if (b.size() > a.size()) {
if (b.substr(0, a.size()) == a) {
ans += dfs(b), ans %= kMod;
}
if (b.substr(b.size() - a.size(), a.size()) == a) {
ans += dfs(b), ans %= kMod;
}
}
}
return dp[s] = ans;
}
int main() {
cin >> s;
dfs(s);
cout << dp[s] - 1 << '\n';
return 0;
}
隔离
题目看着十分困难,其实就只有两种方案:
- 把题目在 B 地全部做完在回来并隔离;
- 每次快要到隔离的时候回去一趟。
注意:如果已经有一个工作超过了 \(240\) 小时,那么直接一口气做完。
#include <iostream>
using namespace std;
using ll = long long;
const ll kMaxN = 1e5 + 5;
ll a[kMaxN], n, s, c, p = 400;
int main() {
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
for (ll i = 1; i <= n; i++) {
s += a[i];
}
for (ll i = 1; i <= n; i++) {
if (a[i] >= 240) {
p = 2e18;
break;
}
if (c + a[i] >= 240) {
c = 0, p += a[i] + 400, c += a[i];
} else {
c += a[i], p += a[i];
}
}
cout << min(s + 10080 + 400, p) << '\n';
return 0;
}
和积
我们直接预处理 \([1,5\times 10^6]\) 的每个数的各个数位的和与积,然后进行大暴力,开上 O2,信仰提交!!!TLE A 了!
#include <iostream>
using namespace std;
using ll = long long;
const ll kMaxN = 5e6 + 5;
ll s[kMaxN], t[kMaxN], T, m, n, k, ans, sol;
ll sum(ll x) {
ll s = 0;
for (; x; x /= 10) {
s += x % 10;
}
return s;
}
ll times(ll x) {
ll s = x % 10;
for (x /= 10; x; x /= 10) {
s *= x % 10;
}
return s;
}
void solve(ll n) {
for (ll i = 1; i <= n; i++) {
s[i] = sum(i), t[i] = times(i);
}
}
int main() {
solve(5e6);
for (cin >> T; T; T--) {
cin >> m >> n >> k, ans = sol = -1;
for (ll i = m; i <= n; i++) {
if (s[i] == k && t[i] > sol) {
ans = i, sol = t[i];
}
}
cout << ans << ' ' << sol << '\n';
}
return 0;
}
对联
我们可以用 set 存下每一个数是否出现过,然后使用 map 记录下每一个数对应的数。对于上联的每一个数,如果已经记录下来了就直接输出;否则继续往大的枚举直到没有出现,记录并输出。
#include <iostream>
#include <set>
#include <unordered_map>
using namespace std;
const int kMaxN = 1e5 + 5;
int a[kMaxN], n, l;
set<int> s;
unordered_map<int, int> f;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i], s.insert(a[i]);
}
for (int i = 1; i <= n; i++) {
if (f.count(a[i])) {
cout << f[a[i]] << ' ';
} else {
for (l++; s.count(l); l++) {
}
cout << (f[a[i]] = l) << ' ';
}
}
return 0;
}
跳跃的排列
仔细观察数据,我们可以发现规律:不是输出 \(1\) 就是输出 \(0\)。因此我们可以得出结论:如果有任意一个顺序对那么就输出 \(1\),否则输出 \(0\)。这样子是 \(\mathcal{O}(n\log_2 n)\) 的,但是这题 \(1\le n\le 10^6\),复杂度可能被卡,因此我们直接暴力枚举:
对于每一个 \(1< i\le n\),如果 \(a_{i-1}<a_i\) 那么直接输出 \(1\),否则输出 \(0\)。
#include <iostream>
#include <algorithm>
using namespace std;
const int kMaxN = 1e6 + 5;
int a[kMaxN], n, ans;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 2; i <= n; i++) {
if (a[i] > a[i - 1]) {
cout << "1\n";
return 0;
}
}
cout << "0\n";
return 0;
}
智力测试题
(过于可爱的题目。。。 直接模拟!!!
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
int n, t, x = 1, y = 1;
int main() {
cin >> n >> t;
for (int i = 1; i <= t; i++) {
if (x != n || y != n) {
y++, (y > n ? x++, y = 1 : 114514);
} else {
x = y = 1;
}
}
cout << x << ' ' << y << '\n';
return 0;
}
防御法阵
我们首先对于每一个城墙进行一个简单的 01 背包,\(t\) 即为重量限制。设 \(dp_i\) 表示使用了 \(i\) 的时间最多能够获得的经验值,状态转移教程:
然后对于每一个城墙,分别求一遍 01 背包,进行序列 \(dp\)。设 \(dp_{(i,j)}\) 表示当前已经破坏掉了 \([i,j]\) 的城墙,所能获得的最大经验值,然后进行转移:
但是这样子 \(N\times N\) 的数组就会逝世,因为 MLE 了。我们知道每次 \(j-i+1\) 最大只能是 \(M\),因此时间复杂度是 \(M^2\) 的,但是我们又懒得写偏移,因此可以直接使用 map 进行转移,然后开 O2 卡过。原谅我加了火车头……
网站的可爱评测机竟然没有卡过!
#include <iostream>
#include <cstring>
#include <map>
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
using namespace std;
using ll = long long;
const ll kMaxN = 10005, kMaxK = 55, kMaxT = 205;
ll v[kMaxK], w[kMaxK], dp[kMaxN][kMaxT], f[kMaxN], pre[kMaxN], n, m, t, k, ans;
map<pair<ll, ll>, ll> DP;
void S() {
for (ll i = 1; i <= n; i++) {
f[i] = dp[i][t];
}
}
ll DymanicProgram(ll x) {
DP.clear();
DP[{x, x}] = f[x];
for (int i = 1; i < m; i++) { // 当前区间长度
for (int j = x - m + 1; j <= x; j++) { // 枚举端点
if (j + i - 1 >= x) {
int l = j, r = j + i - 1; // 左端点和右端点
if (l > 1) {
DP[{l - 1, r}] = max(DP[{l - 1, r}], DP[{l, r}] * 2 + f[l - 1]);
DP[{l, r + 1}] = max(DP[{l, r + 1}], DP[{l, r}] * 2 + f[r + 1]);
}
if (r < n) {
DP[{l, r + 1}] = max(DP[{l, r + 1}], DP[{l, r}] * 2 + f[r + 1]);
}
}
}
}
ll mx = 0;
for (int i = max(1ll, x - m + 1); i <= x && i + m - 1 <= n; i++) {
mx = max(mx, DP[{i, i + m - 1}]);
}
return mx;
}
int main() {
cin >> n >> m >> t;
for (ll i = 1; i <= n; i++) {
cin >> k;
for (ll j = 1; j <= k; j++) {
cin >> v[j] >> w[j];
}
for (ll j = 1; j <= k; j++) {
for (ll l = t; l >= w[j]; l--) {
dp[i][l] = max(dp[i][l], dp[i][l - w[j]] + v[j]);
}
}
}
S();
for (ll i = 1; i <= n; i++) {
ans = max(ans, DymanicProgram(i));
}
cout << ans << '\n';
return 0;
}
采集原石
对于每一个原石,如果它可以获得更多原石,就加入 vector 以所需原石进行排序,因为不管它能获得多少的原石,只要它是能赚的,那么所需原石越少就可以越早买到。排完序直接贪心就可以了。
#include <iostream>
#include <algorithm>
#include <utility>
#include <vector>
using namespace std;
using ll = long long;
ll n, k;
vector<pair<ll, ll>> v;
int main() {
cin >> n >> k;
for (ll i = 1, k, m; i <= n; i++) {
cin >> k >> m, k > m && (v.push_back({k, m}), 114514);
}
sort(v.begin(), v.end(), [](auto a, auto b) {
return a.second < b.second;
});
for (auto i : v) {
if (k < i.second) {
break;
}
k -= i.second, k += i.first;
}
cout << k << '\n';
return 0;
}
字母简记
我们枚举每一个字符,如果是数字就把后面连起来的数字进行计算,加上当前的字符串,然后使用循环进行重复就行了。
#include <iostream>
using namespace std;
int t, n, l;
string s, ans, tmp;
int main() {
for (cin >> t; t; t--) {
cin >> n >> s, l = 0, ans = "";
for (int i = 0; i < n; i++) {
if (isdigit(s[i])) {
int c = 0, p = i;
for (; i < n && isdigit(s[i]); i++) {
c = c * 10 + s[i] - 48;
}
ans += s.substr(l, p - l), tmp = ans;
for (int i = 1; i < c; i++) {
ans += tmp;
}
l = i;
}
}
cout << ans << '\n';
}
return 0;
}
攻与防
过于智慧的前缀和水题。考场上,他们都在说是二分什么的,我真 脏话文明 服了,这明明就是前缀和好不好???设 \(s_i\) 啊不,\(s_i\) 就是 \(\sum\limits_{j=1}^{s_i}a_j\),那么获得 \(\sum\limits_{j=p_1}^{p_2}a_j\) 我们就可以直接计算 \(s_{p_2}-s_{(p_1-1)}\) 就行了,这样就在 \(\mathcal{O}(1)\) 的时间内计算好了任意区间的静态前缀和。
#include <iostream>
#include <cmath>
using namespace std;
using ll = long long;
const ll kMaxN = 1e5 + 5;
ll n, p1[kMaxN], p2[kMaxN], ans = 1e9;
char c;
int main() {
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> c;
p1[i] = p1[i - 1] + (c == '0') * i;
p2[i] = p2[i - 1] + (c == '1') * i;
}
for (ll i = 0; i <= n; i++) {
ans = min(ans, abs(p1[i] - (p2[n] - p2[i])));
}
cout << ans << '\n';
return 0;
}
四月是你的谎言
这题考试时好渴鹅脑抽了,写的正向贪心,WA 了一部分。其实我们在截取字符串的是否将顺序改为逆序就行了,贪心正确性请读者自己想,因为好渴鹅也不知道具体是什么原理。
千万要注意 size_t 不能计算负数!!!
#include <iostream>
using namespace std;
const int kMaxN = 1e5 + 5;
int t, n;
string s[kMaxN], str;
int main() {
for (cin >> t; t; t--) {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> s[i];
}
cin >> str;
for (int i = 0; i < str.size(); i++) {
for (int j = 1; j <= n; j++) {
if (i - (int)s[j].size() + 1 >= 0 && str.substr(i - s[j].size() + 1, s[j].size()) == s[j]) {
str[i] = '*';
}
}
}
cout << str << '\n';
}
return 0;
}
求余来喽
直接暴力枚举。
#include <iostream>
using namespace std;
using ll = long long;
const ll kMaxN = 3005;
ll a[kMaxN], n, l, r, mx = 1e9, ans;
int main() {
cin >> n >> l >> r;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
for (ll k = l; k <= r; k++) {
ll s = 0;
for (ll i = 1; i <= n; i++) {
s += a[i] % k;
}
if (s < mx) {
mx = s, ans = k;
}
}
cout << ans << '\n';
return 0;
}
乘法考验
直接贪心就行。先考虑万能的情况,如果需要 \(k\) 个 \(0\) 那么 \(b\) 就等于 \(10^{k-\log_{10}a}\),此时我们知道 \(10\) 的因数有 \(2\) 和 \(5\),所以 \(k\) 可以除以若干个 \(2\) 和 \(5\)。我们对 \(a\) 进行对 \(2\) 和 \(5\) 分解只因数。设 \(x\) 和 \(y\) 表示 \(a\) 对 \(2\) 和 \(y\) 分解质因数的次数。答案取 \(2^x+5^y\) 就行了。记得判断负数的情况。
#include <iostream>
using namespace std;
using ll = long long;
ll t, a, k, ans, t1, t2, f;
int main() {
for (cin >> t; t; t--) {
cin >> a >> k, t1 = t2 = 0, ans = 1, f = 1;
if (a < 0) {
f = -1, a = -a;
}
for (ll i = 1; i <= k && a % 2 == 0; i++) {
a /= 2, t1++;
}
for (ll i = 1; i <= k && a % 5 == 0; i++) {
a /= 5, t2++;
}
for (ll i = 1; i <= k - t1; i++) {
ans *= 2;
}
for (ll i = 1; i <= k - t2; i++) {
ans *= 5;
}
cout << ans * f << '\n';
}
return 0;
}
回文树
我们知道,一个结点的改变,只会改变父结点,因此我们可以想出一个算法:不停的遍历父结点,并与之前的的结果进行比较,这样子就少了一次 \(\mathcal{O}(n)\) 的遍历。总时间复杂度 \(\mathcal{O}(q\log_2 n)\)。
#include <iostream>
using namespace std;
const int kMaxN = 1e5 + 5;
int f[kMaxN], t[kMaxN * 4][27], s[kMaxN], n, q, ans;
char a[kMaxN], c;
bool check(int x) {
int c = 0;
for (int i = 0; i < 26; i++) {
c += t[x][i] & 1;
}
return (s[x] & 1 ? c == 1 : !c);
}
void build(int x) {
t[x][a[x] - 'a']++, s[x]++;
if (x * 2 <= n) {
build(x * 2), s[x] += s[x * 2];
}
if (x * 2 + 1 <= n) {
build(x * 2 + 1), s[x] += s[x * 2 + 1];
}
for (int i = 0; i < 26; i++) {
t[x][i] += t[x * 2][i] + t[x * 2 + 1][i];
}
}
void update(int x, char c) {
char lst = a[x];
a[x] = c;
for (int i = x; i != 1; i /= 2) {
t[i][lst - 'a']--, t[i][c - 'a']++;
}
t[1][lst - 'a']--, t[1][c - 'a']++;
}
void query(int x) {
for (int i = x; i != 1; i /= 2) {
int p = check(i);
if (p) {
ans += (f[i] != 1);
} else {
ans -= (f[i] == 1);
}
f[i] = p;
}
int p = check(1);
if (p) {
ans += (f[1] != 1);
} else {
ans -= (f[1] == 1);
}
f[1] = p;
}
int main() {
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
build(1);
for (int i = 1; i <= n; i++) {
if (check(i)) {
f[i] = 1, ans++;
}
}
cout << ans << '\n';
for (int x; q; q--) {
cin >> x >> c;
update(x, c);
query(x);
cout << ans << '\n';
}
return 0;
}
完结散花!

浙公网安备 33010602011771号