C. Security 2
要从反向模拟入手!
逆向思考后:
- 按钮 \(A\):移除末尾的0
- 按钮 \(B\):将S的所有数字减1
此时只需反复执行“按按钮 \(B\) 直到 \(S\) 末尾变为 \(0 \to\) 按按钮 \(A\) ” 即可。
如果每次按按钮 \(B\) 都实际改写整个字符串 \(S\),复杂度会达到 \(O(|S|^2)\) 导致TLE。其实只需记录按钮 \(B\) 被按过的次数 \(x\),现场计算当前 \(S\) 末尾的值即可!实际上这个值不需要真的算出来,只需判定末尾的值是否是 \(0\) 即可。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
int main() {
string s;
cin >> s;
int ans = 0, x = 0;
while (s != "") {
while (1) {
int d = s.back()-'0';
if ((d-x)%10 == 0) break;
ans++; x++;
}
s.pop_back();
ans++;
}
cout << ans << '\n';
return 0;
}
D. Domino Covering XOR
只需从左上角的格子开始,按顺序尝试「纵向放置多米诺」「横向放置多米诺」「不放置」这三种选择进行DFS就行了!
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int h, w;
cin >> h >> w;
vector a(h, vector<ll>(w));
rep(i, h)rep(j, w) cin >> a[i][j];
ll ans = 0;
vector covered(h, vector<bool>(w));
auto f = [&](auto& f, int i, int j, ll x) -> void {
if (j == w) j = 0, i++;
if (i == h) {
ans = max(ans, x);
return;
}
if (covered[i][j]) {
f(f, i, j+1, x);
}
else {
f(f, i, j+1, x^a[i][j]);
if (j+1 < w and !covered[i][j+1]) {
covered[i][j] = covered[i][j+1] = true;
f(f, i, j+1, x);
covered[i][j] = covered[i][j+1] = false;
}
if (i+1 < h and !covered[i+1][j]) {
covered[i][j] = covered[i+1][j] = true;
f(f, i, j+1, x);
covered[i][j] = covered[i+1][j] = false;
}
}
};
f(f, 0, 0, 0);
cout << ans << '\n';
return 0;
}
E. Most Valuable Parentheses
贪心
由 \(N\) 个左括号和N个右括号组成的字符串要构成合法括号序列的充要条件就是『任何前缀中左括号数都不少于右括号数』。
所以问题就转化为:『在长度为 \(2N\) 的序列中选 \(N\) 个数标记,要求任何前缀中被标记数的数量都不少于未标记数,求被标记数之和的最大值』。这个数量条件等价于『对于任意奇数 \(x\),前 \(x\) 位中至少有 \(\lceil\frac{x}{2}\rceil\) 个标记』,由于 \(x\) 越小条件越严格,所以只需要满足:
- 前 \(1\) 位至少有 \(1\) 个标记
- 前 \(3\) 位至少有 \(2\) 个标记
- 前 \(5\) 位至少有 \(3\) 个标记
- \(\cdots\cdots\)
于是我们采取『在集合中先添加两个数,再标记其中一个』的贪心策略即可。用大根堆就能实现!
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
void solve() {
int n;
cin >> n;
n *= 2;
vector<int> a(n);
rep(i, n) cin >> a[i];
ll ans = a[0];
priority_queue<int> q;
for (int i = 1; i < n-1; i += 2) {
q.push(a[i]);
q.push(a[i+1]);
ans += q.top(); q.pop();
}
cout << ans << '\n';
}
int main() {
int t;
cin >> t;
while (t--) solve();
return 0;
}
F. Sums of Sliding Window Maximum
按照 \(A_i\) 的值从大到小处理『最大值恰好为 \(A_i\) 的连续子序列有多少个?』这个问题。只需要能实现等差数列的区间加法操作即可,用延迟线段树就能搞定!如果巧妙处理的话,用差分也可以实现。
再讲一下差分做法:
以 \(A_i\) 作为最大值时,将包含 \(A_i\) 的区间所产生的贡献加入答案(由于是区间线性关系,可用差分进行一次函数的区间加法计算)。不包含最大值的部分则分别在左右两侧递归处理,通过笛卡尔树(Cartesian Tree)实现递归。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
template<class T=long long>
struct CartesianTree {
int n, root;
vector<int> l, r;
CartesianTree() {}
CartesianTree(const vector<T>& a, bool _max=true) {
n = a.size();
l = r = vector<int>(n, -1);
vector<int> st;
rep(i, n) {
int p = -1;
while (st.size() and !((a[st.back()] < a[i]) ^ _max)) {
int j = st.back(); st.pop_back();
r[j] = p; p = j;
}
l[i] = p;
st.push_back(i);
}
rep(i, st.size()-1) r[st[i]] = st[i+1];
root = st[0];
}
};
int main() {
int n;
cin >> n;
vector<int> a(n);
rep(i, n) cin >> a[i];
CartesianTree<int> t(a);
vector<ll> d(n+2);
auto f = [&](auto& f, int v) -> int {
if (v == -1) return 0;
int l = f(f, t.l[v])+1;
int r = f(f, t.r[v])+1;
d[0] += a[v]; d[l] -= a[v];
d[r] -= a[v]; d[l+r] += a[v];
return l+r-1;
};
f(f, t.root);
rep(i, n+1) d[i+1] += d[i];
rep(i, n+1) d[i+1] += d[i];
rep(i, n) cout << d[i] << '\n';
return 0;
}
G. Domino Covering SUM
正难则反,可以先求出被覆盖的格子的权值总和,然后用网格总权值减去它就得到了最终的答案。要使答案最大,那么就该让被覆盖的格子的权值和最小
将格点作为顶点,在相邻格点之间建一条边权为 \(A_{ij}+A_{i'j'}\) 的边构成一个二分图,然后求最小权匹配即可。实际操作时,可以先将所有边权加上一个常数 \(G\) 使之为非负数,再求最小费用流,最终结果就是 \(\min_k(\) 流量为 \(k\) 时的最小费用 \(- kG)\)!
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
using ll = long long;
int main() {
int h, w;
cin >> h >> w;
vector a(h, vector<ll>(w));
rep(i, h)rep(j, w) cin >> a[i][j];
const ll G = 2e12;
int n = h*w;
int sv = n, tv = sv+1;
mcf_graph<int, ll> g(tv+1);
rep(i, h)rep(j, w) {
int v = i*w+j;
if (i+j&1) {
g.add_edge(v, tv, 1, 0);
}
else {
g.add_edge(sv, v, 1, 0);
}
auto add = [&](int ni, int nj) {
int u = ni*w+nj;
if (i+j&1) {
g.add_edge(u, v, 1, a[i][j]+a[ni][nj]+G);
}
else {
g.add_edge(v, u, 1, a[i][j]+a[ni][nj]+G);
}
};
if (i) add(i-1, j);
if (j) add(i, j-1);
}
ll tot = 0;
rep(i, h)rep(j, w) tot += a[i][j];
ll ans = -1e18;
for (auto [flow, cost] : g.slope(sv, tv)) {
ll now = tot - (cost-G*flow);
ans = max(ans, now);
}
cout << ans << '\n';
return 0;
}
浙公网安备 33010602011771号