Codeforces Round 966 (Div. 3)
Codeforces Round 966 (Div. 3)
A. Primary Task
思路
\(false\) 的情况:1、\(s.size()\le 2\) ;2、\(s\) 不以10
开头;3、\(s\) 2位以后得字符串转整型后小于 \(2\) 或者含有前导零。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
string s;
cin >> s;
if (s.size() <= 2) {
cout << "NO\n";
return ;
}
cout << (s.substr(0, 2) == "10" && stoi(s.substr(2)) >= 2 && to_string(stoi(s.substr(2))) == s.substr(2) ? "YES" : "NO") << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
B. Seating in a Bus
思路
用 \(set\) 维护当前位是不是等于已插入数 最大+1
或者最小-1
。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
set<int> s;
s.insert(a[1]);
for (int i = 2; i <= n; i ++) {
if (a[i] == *s.rbegin() + 1 || a[i] == *s.begin() - 1) {
s.insert(a[i]);
} else {
cout << "NO\n";
return ;
}
}
cout << "YES\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
C. Numeric String Template
思路
预处理数字串中相同数字的所有位置,然后对字符串也进行相应的处理,最后判断字符串的位置是否合法即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
map<int, int> mp;
vector<int> a(n);
vector ve(n, vector<int>());
for (int i = 0; i < n ; i ++) {
cin >> a[i];
if (!mp.count(a[i])) {
mp[a[i]] = i;
ve[i].push_back(i);
} else {
ve[mp[a[i]]].push_back(i);
}
}
int m;
cin >> m;
while (m--) {
string s;
cin >> s;
if (s.size() != n) {
cout << "NO\n";
continue;
}
map<int, int> mp1;
vector Ve(n, vector<int>());
for (int i = 0; i < s.size(); i ++) {
if (!mp1.count(s[i])) {
mp1[s[i]] = i;
Ve[i].push_back(i);
} else {
Ve[mp1[s[i]]].push_back(i);
}
}
cout << (Ve == ve ? "YES\n" : "NO\n");
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D. Right Left Wrong
思路
要尽可能地获取最高分,那么最左边的L
就要尽可能地去匹配最右边的R
,处理出两者的位置,然后用前缀和求和即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
vector<i64> a(n + 1), pre(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
pre[i] = pre[i - 1] + a[i];
}
string s;
cin >> s;
s = " " + s;
vector<int> l, r;
for (int i = n; i >= 1; i --) {
if (s[i] == 'R') {
r.push_back(i);
}
}
for (int i = 1; i <= n; i ++) {
if (s[i] == 'L') {
l.push_back(i);
}
}
if (l.empty() || r.empty()) {
cout << 0 << '\n';
return ;
}
i64 ans = 0;
int idx = 0;
for (auto i : l) {
if (r[idx] < i || idx >= r.size())
break;
ans += pre[r[idx]] - pre[i - 1];
idx ++;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E. Photoshoot for Gorillas
思路
注意到 $1\le n\cdot m\le 1e5 $,那么可以直接二维差分前缀和处理矩形的覆盖范围,然后将覆盖的值取出从大到小排序和 \(a_i\) 对应即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
i64 n, m, k, w;
cin >> n >> m >> k >> w;
vector<i64> a(w);
for (int i = 0; i < w; i ++) {
cin >> a[i];
}
sort(a.begin(), a.end(), greater<>());
vector v(n + 2, vector<int>(m + 2));
auto sum = v;
for (int i = 1; i + k <= n + 1; i ++) {
for (int j = 1; j + k <= m + 1; j ++) {
v[i][j] ++, v[i][j + k] --;
v[i + k][j]--, v[i + k][j + k]++;
}
}
vector<i64> d;
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++) {
v[i][j] += v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1];
d.push_back(v[i][j]);
}
}
sort(d.begin(), d.end(), greater<>());
i64 ans = 0;
for (int i = 0; i < w; i ++) {
ans += d[i] * a[i];
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
F. Color Rows and Columns
思路
一个长为 \(a\),宽为 \(b\) 的矩形可以产生的贡献应该是 \(0,1,2,\dots a+b-3,a+b-2,a+b\),诶?\(a+b-1\) 呢?试想一下,当矩形中只差一个未涂满的情况下,长和宽分别是 \(a-1,b-1\),这个时候产生的贡献为 \(a+b-2\),那么当我们填上这一个点,长和宽就会刚好被涂满,从而产生 \(a+b\) 的贡献,所以 \(a+b-1\) 的情况是不会产生的。
根据以上结论,我们可以处理出每个矩形获得相应点数的代价和贡献,然后从每个矩形中选择一个贡献(不选择以贡献为0的形式展现)出来,然后得到至少为 \(k\) 的最小代价。
没错,这就是分组背包典型模板了,值得注意的是题目中要求的是至少为 \(k\) 的最小代价,也就是说可能存在分数大于 \(k\) 且代价更小的情况,这个时候我们背包的范围就要取大一点,至少也得 \(k+1\)(不要刚好取 \(100\),\(k\) 也可以等于\(100\))。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k;
cin >> n >> k;
vector w(n + 1, vector<array<i64, 2>>());
vector<array<int, 2>> a(n + 1);
for (int j = 1, x, y; j <= n; j ++) {
cin >> x >> y;
a[j] = {x, y};
int tx = x, ty = y;
for (int i = 1, sum = 0; i <= x + y; i ++) {
if (i == x + y - 1) continue;
if (i == x + y) {
w[j].push_back({i, x * y});
} else {
sum += min(tx, ty);
tx > ty ? tx-- : ty--;
w[j].push_back({i, sum});
}
}
}
const int N = 114, M = N - 10;
vector<i64> dp(N, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i ++) {
for (int j = M; j >= 0; j --) {
for (auto &[cost , val] : w[i]) {
if (j < cost) continue;
dp[j] = min(dp[j], dp[j - cost] + val);
}
}
}
i64 ans = -1;
for (int i = k; i <= M; i ++) {
if (dp[i] != INT_MAX) {
if (ans == -1) ans = dp[i];
else ans = min(ans, dp[i]);
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
G. Call During the Journey
思路
考虑要找最晚出发的时间,这点我们可以通过二分解决。
然后考虑 Dijkstra,如果当前的时间点采用骑车的话,其花费的时间段和打电话的时间如果有交集,那我们只有两种选择,走路 或者 在原地打完电话之后骑车;若无交集,那就正常骑车即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct DIJ {
using i64 = long long;
using PII = pair<i64, i64>;
vector<i64> dis;
vector<vector<array<i64, 3>>> G;
int n;
DIJ() {}
DIJ(int n_): n(n_) {
G.resize(n + 1);
}
void add(int u, int v, int l1, int l2) {
G[u].push_back({v, l1, l2});
}
void dijkstra(int s, i64 tx, i64 t1, i64 t2) {
dis.assign(n + 1, 1e18);
priority_queue<PII> Q;
dis[s] = tx;
Q.push({ -dis[s], s});
while (!Q.empty()) {
auto [t, u] = Q.top();
Q.pop();
t = -t;
if (dis[u] < t) continue;
for (auto [v, l1, l2] : G[u]) {
auto now = t + l2;
if (max(t, t1) < min(t + l1, t2)) {
now = min(now, t2 + l1);
} else {
now = min(now, t + l1);
}
if (dis[v] > now) {
dis[v] = now;
Q.push({ -dis[v], v});
}
}
}
}
};
void solve() {
int n, m, t[3] {};
cin >> n >> m >> t[0] >> t[1] >> t[2];
DIJ dij(n);
for (int i = 0; i < m; i ++) {
int u, v, l1, l2;
cin >> u >> v >> l1 >> l2;
dij.add(u, v, l1, l2);
dij.add(v, u, l1, l2);
}
i64 l = 0, r = t[0], ans = -1;
while (l <= r) {
i64 mid = l + r >> 1;
dij.dijkstra(1, mid, t[1], t[2]);
if (dij.dis[n] <= t[0]) l = mid + 1, ans = mid;
else r = mid - 1;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
H. Ksyusha and the Loaded Set
思路
考虑题中所给 \(k\) 的性质,其实就是在集合中从左往右找到第一段两两相差长度至少为 \(k\) 的位置。
所以我们可以维护一个权值线段树,对于集合中出现的数,我们可以在线段树中标记为 \(1\),那么两个 \(1\) 之间相差的 \(0\) 的个数不就等于两数相差的长度吗,那我们只用维护这个区间中最大的 \(0\) 的个数即可,想到这,其实就可以发现这是个比较板的维护 \(0/1\) 串中最大连续 \(0\) 的个数线段树,对于找长度为 \(k\) 的位置,我们可以线段树上二分,因为是从左往右找,所以找完左边还得看下中间是否符合,即判断左区间后缀连续 \(0\ +\)右区间前缀连续 \(0\) 是否 \(\ge k\),最后找右区间。
每次可以维护一个 \(set\) 代表集合中的数,最后将线段树清空,减少每次新开线段树带来的时间和空间上的损耗。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
template<class Node>
struct SegmentTree {
#define lc u<<1
#define rc u<<1|1
const int n, N;
vector<Node> tr;
SegmentTree(): n(0) {}
SegmentTree(int n_): n(n_), N(n * 4 + 10) {
tr.reserve(N);
tr.resize(N);
build(1, 1, n);
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) {
tr[u] = {l, r, 1, 1, 1};
return ;
}
i64 mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(tr[u], tr[lc], tr[rc]);
};
void pushup(Node& U, Node& L, Node& R) { //上传
U.l = L.l, U.r = R.r;
if (L.presum == L.r - L.l + 1) {
U.presum = L.presum + R.presum;
} else {
U.presum = L.presum;
}
if (R.lastsum == R.r - R.l + 1) {
U.lastsum = L.lastsum + R.lastsum;
} else {
U.lastsum = R.lastsum;
}
U.Maxsum = max({L.Maxsum, R.Maxsum, L.lastsum + R.presum});
}
void modify(int u, int pos) {
if (tr[u].l >= pos && tr[u].r <= pos) {
tr[u].Maxsum ^= 1;
tr[u].presum = tr[u].lastsum = tr[u].Maxsum;
return ;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (pos <= mid)
modify(lc, pos);
else
modify(rc, pos);
pushup(tr[u], tr[lc], tr[rc]);
}
int query(int u, int k) { //区查
if (tr[u].l == tr[u].r)
return tr[u].l;
if (tr[lc].Maxsum >= k)
return query(lc, k);
if (tr[lc].lastsum + tr[rc].presum >= k) {
return tr[lc].r - tr[lc].lastsum + 1;
}
return query(rc, k);
}
};
struct Node { //线段树定义
i64 l, r;
i64 presum, lastsum, Maxsum;
};
constexpr int N = 2e6 + 10, M = N - 10;
SegmentTree<Node> tr(M);
void solve() {
set<int> s;
int n;
cin >> n;
for (int i = 1; i <= n; i ++) {
int x;
cin >> x;
s.insert(x);
tr.modify(1, x);
}
int m;
cin >> m;
while (m--) {
char op;
int x;
cin >> op >> x;
if (op == '+') {
s.insert(x);
tr.modify(1, x);
} else if (op == '-') {
s.erase(x);
tr.modify(1, x);
} else {
if (x > tr.tr[1].Maxsum) {
cout << M - tr.tr[1].lastsum + 1 << ' ';
} else {
cout << tr.query(1, x) << ' ';
}
}
}
cout << '\n';
for (auto &x : s) {
tr.modify(1, x);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}