Codeforces Round 991 (Div. 3) vp 题解
A
纯模拟,模拟一下就可以了。
B
呃,挺好发现的吧,每一次只能选择 i-1
和 i+1
这两个位置,就决定了这道题中,一个数组中只有同奇偶性的位置的数字可以均摊和,所以我们只需要分别算出奇数位置和偶数位置的平均值看看相不相等就行了。
C
挺好的一道数学题。
首先分析数字的构成,不难发现可以变成这个样子:
然后题目要求能被 9 整除,所以我们可以继续拆解成这个样子:
欸,然后我们发现只需要去考虑最后的 \(a_1 + a_2 + \dots + a_n\) 能不能被 9 整除就行了。
此时我们还要考虑,如果不能的话我们需要用平方去补全,但是根据观察,发现允许平方的只有 2, 3
这两个数字,而且观察到 2
平方一次可以多加一个 2
,3
平方一次会多加一个 6
,此时我们想到 \(2 \times 9\) 与 \(3 \times 6\),发现至多只有 \(8\) 个 \(2\) 和 \(2\) 个 \(3\) 平方,否则就会因为可以被 9 整除而失去意义。
所以我们可以直接枚举 \(\min(cnt_2, 8)\) 和 \(\min(cnt_3, 2)\) 来得到可能得到的用于补全的数字,看看有没有符合要求的即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
void solve () {
string n; cin >> n;
ll sum = 0, _2 = 0, _3 = 0;
for (auto c : n) {
sum += (c-'0');
if (c == '2') _2++;
else if (c == '3') _3++;
}
sum = sum % 9;
if (!sum) { cout << "YES\n"; return; }
// sum = 9 - sum;
// if (!sum) { cout << "YES\n"; return; }
ll ans = 0;
for (int i = 0;i <= min(8ll, _2);i++) {
ans = i*2;
if (!((ans+sum) % 9)) { cout << "YES\n"; return; }
for (int j = 0;j <= min(_3, 2ll);j++) {
ans = i*2 + j*6;
// cout << ans << "----- " << j << "\n";
if (!((ans+sum) % 9)) { cout << "YES\n"; return; }
}
}
cout << "NO\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
D
一道比较好想的贪心题,直接从左往右枚举来看,而且考虑到一个数字最多和后面 9 位的字母进行交换操作,所以可以直接枚举和模拟。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
void solve () {
string s; cin >> s;
int len = s.size();
for (int i = 0;i < len;i++) {
int pos = i, maxn = s[i] - '0';
for (int j = i + 1;j < min(len, i+9);j++)
if ((s[j] - '0') - (j - i) > maxn)
pos = j, maxn = (s[j] - '0') - (j - i);
char tmp = maxn + '0';
for (int j = pos;j > i;j--) swap(s[j], s[j-1]);
s[i] = tmp;
}
cout << s << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
E
一道挺好的动态规划题,这里不难发现对于字符串 \(c\) 而言,它是由一段 \(a\) 和一段 \(b\) 的前缀经过一些变化得到的,是一种线性的过程,并且后面的状态不会对前面的有影响,不具有后效性,所以哦我们考虑动态规划。
我们设 \(dp_{i, j}\) 为使用了长度为 \(i\) 的 \(a\) 前缀和长度为 \(j\) 的 \(b\) 的前缀的最优答案,此时发现我们需要根据 \(a\) 和 \(b\) 的长度先进行初始化,也就是不用对方的任何一个前缀只用自己的,此时就是单纯的匹配找不同。
此时思考转移方程,发现我们对于字符串 \(c\) 的每一位我们要考虑同字符串 \(a\) 和 字符串 \(b\) 分别比较和讨论,那么可以得到:
然后我们输出 \(dp_{len_a, len_b}\) 即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N = 1e3+100;
ll dp[N][N];
void solve () {
string a, b, c;
cin >> a >> b >> c;
int lena = a.size(), lenb = b.size();
memset(dp, 0, sizeof(dp));
a = " " + a, b = " " + b, c = " " + c;
for (int i = 1;i <= lena;i++) dp[i][0] = dp[i-1][0] + (a[i] != c[i]);
for (int i = 1;i <= lenb;i++) dp[0][i] = dp[0][i-1] + (b[i] != c[i]);
for (int i = 1;i <= lena;i++)
for (int j = 1;j <= lenb;j++)
dp[i][j] += min(dp[i-1][j] + (a[i] != c[i+j]), dp[i][j-1] + (b[j] != c[i+j]));
cout << dp[lena][lenb] << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
F
绷,板子区间题。
这里看题目给出的式子:\(x \mod m = y \mod m\),发现可以直接变成:\(|x-y| \mod m = 0\)。
有点意思,说明可以直接将数组 \(a_1, a_2, a_3, a_4 \dots a_n\) 变成 \(|a_1-a_2|, |a_2-a_3| \dots |a_{n-1} - a_n|\),然后建个线段树直接找 \(\gcd\),注意如果全部相等需要直接输出 \(0\)。
这里用的是线段树。
#include <bits/stdc++.h>
// #include <numeric>
using namespace std;
#define ll long long
#define ull unsigned long long
#define lson k << 1
#define rson k << 1 | 1
const int N = 2e5+100;
const int INF = 1e9+100;
struct T {
ll l, r, gcd, minn;
};
void solve () {
int n, q; cin >> n >> q;
vector <int> a(n+1), b(n+1);
vector <T> tr((n*4));
auto pushup = [&] (int k) -> void {
tr[k].gcd = __gcd(tr[lson].gcd, tr[rson].gcd);
// tr[k].minn = min(tr[lson].minn, tr[rson].minn);
};
auto build = [&](auto self, int k, int l, int r) -> void {
tr[k].l = l, tr[k].r = r;
if (l == r) { tr[k].gcd = b[l]; return; }
int mid = (l + r) >> 1;
self(self, lson, l, mid);
self(self, rson, mid+1, r);
pushup(k);
};
auto query = [&](auto self, int k, int l, int r) -> ll {
ll res = 0;
if (tr[k].l >= l && tr[k].r <= r) return tr[k].gcd;
ll mid = (tr[k].l + tr[k].r) >> 1;
if (mid >= l) res = self(self, lson, l, r);
if (mid < r) res = __gcd(res, self(self, rson, l, r));
return res;
};
for (int i = 1;i <= n;i++) {
cin >> a[i];
if (i == 1) b[i] = 0;
else b[i] = abs(a[i-1] - a[i]);
}
build(build, 1, 1, n);
for (int i = 1;i <= n;i++) b[i] += b[i-1];
while (q--) {
int l, r; cin >> l >> r;
if (!(b[r] - b[l-1])) { cout << 0 << " "; continue; }
ll ans = query(query, 1, l+1, r);
// ll tmp = query_min(query_min, 1, l, r);
cout << ans << " ";
}
cout << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}
G
也是挺好写的,发现删掉一个路径对于一个点(不包括端点)来说是删掉了两个子树(也就是两个度),而对于端点而言就是删掉了一个度。所以可以将一个路径的贡献转变为 \(\text{路径总点数的度数} - \text{路径总点数}\times2 + 2\),这里 \(+2\) 因为端点只是减一。
然后发现其实我们就是要每次找最大的,发现跟找直径有异曲同工之妙,可以考虑用 dp 来写,这样好理解。
具体就是每次在一个节点的子树里边找到这个贡献最大的路径,然后在所有子树里找到第一大和第二大的路径组合起来就行了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pii pair <int, int>
struct R {
int u, v, lst;
};
void solve () {
int n; cin >> n;
vector <ll> final(n+1), de(n+1), dp(n+2);
vector <R> E(n<<1);
int tot = 0;
ll ans = 0;
auto add = [&](int u, int v) -> void { E[++tot].u = u, E[tot].v = v, E[tot].lst = final[u], final[u] = tot; };
auto dfs = [&](auto self, int now, int fa) -> void {
ll tmp = de[now] - 2;
dp[now] = tmp;
ans = max(ans, tmp+2);
// cout << ans << "[+]" << "\n";
for (int i = final[now];i;i = E[i].lst) {
int to = E[i].v;
if (to == fa) continue;
self(self, to, now);
ans = max(ans, dp[now] + dp[to] + 2);
dp[now] = max(dp[now], tmp + dp[to]);
}
};
for (int i = 1;i < n;i++) {
int u, v; cin >> u >> v;
add(u, v), add(v, u); de[u]++, de[v]++;
}
// vector <pii> dp(n+1);
dfs(dfs, 1, 0);
cout << ans << "\n";
}
int main () {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int _ = 1; cin >> _;
while (_--) solve();
return 0;
}