Educational Codeforces Round 161 (Rated for Div. 2)
https://codeforces.com/contest/1922
相当无聊的一场。E题勉强有一丁点意思,F题还行。
A. Tricky Template *800
对包含大小写字母的模式串 \(t\),称只包含小写字母的串 \(s\) 与 \(t\) 匹配,当且仅当对于每个 \(i\),若 \(t_i\) 为小写则 \(s_i=t_i\),若 \(t_i\) 为大写则 \(s_i\neq t_i\)。
给定三个小写串 \(a,b,c\),问是否存在模式串使得 \(a,b\) 匹配且 \(c\) 不匹配。
分类讨论一下即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
string sol() {
int n; string a, b, c;
cin >> n >> a >> b >> c;
for (int i = 0; i < n; i++) {
if (a[i] == b[i] && a[i] != c[i]) return "YES";
if (a[i] != b[i] && a[i] != c[i] && b[i] != c[i]) return "YES";
}
return "NO";
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
cout << sol() << '\n';
}
B. Forming Triangles *1200 入门计数题
给定 \(n\) 根木棒的长度,第 \(n\) 根木棒的长度是 \(2^{a_i}\)。问能组成的非退化三角形的个数。
\(n\le 3e5\)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
int n;
cin >> n;
vector<ll> c(n + 1); //桶
for (int i = 0; i < n; i++) {
int x; cin >> x; c[x]++;
}
ll ans = 0;
for (int i = 0, s = 0; i <= n; i++) {
if (c[i] > 0) ans += c[i] * (c[i] - 1) / 2 * s;
if (c[i] > 1) ans += c[i] * (c[i] - 1) * (c[i] - 2) / 6;
s += c[i];
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
C. Closest Cities *1300 语法题
给定数轴上 \(n\) 个城市的位置,每个城市的最近城市唯一。走到最近城市的时间是 \(1\),走到非最近城市的时间是距离,\(m\) 次询问从一地走到另一地的时间。
模拟。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
int n, m;
cin >> n;
vector<ll> a(n + 2), f(n + 1), g(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
a[0] = -1e12, a[n + 1] = 1e12;
for (int i = 2; i <= n; i++) //从左到右走
f[i] = f[i - 1] + (a[i] - a[i - 1] < a[i - 1] - a[i - 2] ? 1 : a[i] - a[i - 1]);
for (int i = n - 1; i; i--) //从右到左走
g[i] = g[i + 1] + (a[i + 1] - a[i] < a[i + 2] - a[i + 1] ? 1 : a[i + 1] - a[i]);
cin >> m;
while (m--) {
int x, y;
cin >> x >> y;
if (x < y) cout << f[y] - f[x] << '\n';
else cout << g[y] - g[x] << '\n';
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
D. Berserk Monsters *1900 链表模拟题
每个怪有攻击和防御,每个回合每个怪受相邻(一个或两个)怪的攻击,受的总攻击 \(>\) 自己的防御则死,每回合后活着的怪重新紧凑分布。问每回合死几只怪。
\(n\le 3e5\)
如果一个怪的邻居本回合都没死,他自己也没死,那他下一回合也不会死,不用考虑他。链表模拟,vector套vector记录每回合死了谁,每回合只用考虑上一回合死的怪的邻居。
很无聊,好像适合出成CSP-J组题
复杂度 \(O(n)\),别老想着set
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
int n;
cin >> n;
vector<int> a(n + 1), d(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> d[i];
vector<int> l(n + 1), r(n + 1);
for (int i = 1; i <= n; i++) {
l[i] = i - 1;
r[i] = i + 1;
}
r[n] = 0;
vector<bool> died(n + 1);
vector<vector<int>> ve(n + 1); //每次死的怪
died[0] = true;
for (int i = 1; i <= n; i++) ve[0].push_back(i);
auto check = [&](int p) { //检查p本轮能不能死
if (!died[p] && a[l[p]] + a[r[p]] > d[p]) {
died[p] = true;
return true;
}
return false;
};
for (int i = 1; i <= n; i++) {
for (int p : ve[i - 1]) {
if (check(l[p])) ve[i].push_back(l[p]);
if (check(r[p])) ve[i].push_back(r[p]);
}
for (int p : ve[i]) {
l[r[p]] = l[p];
r[l[p]] = r[p];
}
}
for (int i = 1; i <= n; i++)
cout << ve[i].size() << ' ';
cout << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
E. Increasing Subsequences *1800 简单构造
构造长度不超过 \(200\) 且上升子序列数为 \(x\) 的数列 \(a[]\)。
\(x\le 1e18\),空序列也算入。
注意到严格递增数列的上升子列数为 \(2^n\),考虑二进制。
从高到低考虑 \(x\) 的二进制位,若 \(x\) 有 \(i\) 这个位则把一个长度为 \(i\) 且最大值小于之前任何数的严格递增数列接到答案之尾。
这样长度会超过 \(200\)。考虑对前面的严格递增子列进行复用,对于 \(x\) 的每个非最高位只需新增一个数。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
ll x; cin >> x;
vector<int> a;
int w = __lg(x);
for (int i = 1; i <= w; i++)
a.push_back(2 * i - 1);
x -= 1ll << w;
while (x) {
int w = __lg(x);
a.push_back(w * 2);
x -= 1ll << w;
}
cout << a.size() << '\n';
for (auto i : a) cout << i << ' ';
cout << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
F. Replace on Segment *2500 区间dp
给定整数数组 \(a[]\),\(a_i\in [1,x]\)。每次可选一区间,把区间中的数都变成一个区间中没有的数(仍要在 \([1,x]\) 内),问使所有数相等的最小操作次数。
\(1\le x\le n\le 100\)
(开始想到贪心选择把数组划分成的非空区间数量最少的数,被样例3 hack了。)
显然区间dp。\(f(l,r,num)\) 把区间中的所有数变成 \(num\) 的最小操作次数,(这题的独特之处是)还要开个 \(g(l,r,num)\) 表示把区间中的所有数变成都不是 \(num\) 的最小操作次数。
注意 \(g\) 数组的更新有两种方式,一种是把区间全变成某个不为 \(num\) 的数(让区间中的所有数相等),第二种是左右子区间中没有 \(num\) 就行,但不要求区间中的数都相等。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
int n, x;
cin >> n >> x;
vector f(n + 1, vector(n + 1, vector<int>(x + 1, 1e9)));
vector g(n + 1, vector(n + 1, vector<int>(x + 1, 0)));
for (int i = 1; i <= n; i++) {
int a; cin >> a;
fill(f[i][i].begin(), f[i][i].end(), 1);
f[i][i][a] = 0;
g[i][i][a] = 1;
}
for (int len = 2; len <= n; len++) {
for (int l = 1, r = len; r <= n; l++, r++) {
//更新f
for (int k = l; k < r; k++) {
for (int a = 1; a <= x; a++) {
f[l][r][a] = min({f[l][r][a],
f[l][k][a] + f[k + 1][r][a],
g[l][k][a] + g[k + 1][r][a] + 1});
}
}
//第一种得到g的方法
for (int a = 1, premin = 1e9; a <= x; a++) {
g[l][r][a] = premin;
premin = min(premin, f[l][r][a]);
}
for (int a = x, sufmin = 1e9; a >= 1; a--) {
g[l][r][a] = min(g[l][r][a], sufmin);
sufmin = min(sufmin, f[l][r][a]);
}
//第二种得到g的方法
for (int k = l; k < r; k++) {
for (int a = 1; a <= x; a++) {
g[l][r][a] = min(g[l][r][a], g[l][k][a] + g[k + 1][r][a]);
}
}
}
}
cout << *min_element(f[1][n].begin(), f[1][n].end()) << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}