Codeforces Round 970 (Div. 3)
Codeforces Round 970 (Div. 3)
A. Sakurako's Exam
思路
直接枚举即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int a, b;
cin >> a >> b;
for (int i = 0; i <= a; i++) {
for (int j = 0; j <= b; j++) {
if (i + j * 2 == (a - i) + (b - j) * 2) {
cout << "YES\n";
return ;
}
}
}
cout << "NO\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
B. Square or Not
思路
首先判断一下 \(n\) 是不是完全平方数,然后就看第一行和最后一行是不是全 \(1\),中间的 \(2\sim \sqrt{n}-1\) 行是不是 \(100\dots001\) 这种。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
int m = sqrt(n);
if (m * m != n) {
cout << "No\n";
return ;
}
string a = string(m, '1'), b = "1" + string(m - 2, '0') + "1";
if (s.substr(0, m) != a || s.substr(n - m) != a) {
cout << "No\n";
return ;
}
for (int i = m; i < n - m; i += m) {
if (s.substr(i, m) != b) {
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. Longest Good Array
思路
最优的情况就是相差 \(1,2,3\dots\) 其差值序列为公差为 \(1\) 的等差数列,那么每一项也就是 \(1,2,4,7\dots\) 这样去构造,具体地,仔细观察得出第 \(i\) 项就是 \(1\) 加上差值数列的前 \(i-1\) 项和。
所以我们可以去二分找出 \(r-l\) 的差值里最大的前 \(x\) 项和(解方程也可以),那么就说明它最多可以组成 \(x+1\) 项了。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
i64 a, b;
cin >> a >> b;
i64 l = 1, r = 1e9, ans = 0;
while (l <= r) {
i64 mid = l + r >> 1;
if ((1 + mid) * mid <= (b - a) * 2) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
cout << ans + 1 << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D. Sakurako's Hobby
思路
手玩几个样例大概就能猜出结论,由于是排列的特性,所以最后能够互相到达的点就构成了一个环,那么 \(F(i)\) 就等于这个环里黑点的个数。
可以把这些环看成一个个联通块,用并查集维护黑点的个数即可。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct UFS {
int sz;
vector<int> rank, p, w;
UFS(int n): sz(n) {
rank.resize(n + 1);
p.resize(n + 1);
w.resize(n + 1);
for (int i = 0; i <= sz; i++) {
p[i] = i;
rank[i] = 0;
}
}
void link(int x, int y) {
if (x == y)
return;
if (rank[x] > rank[y]) {
p[y] = x;
w[x] += w[y];
}
else {
p[x] = y;
w[y] += w[x];
}
if (rank[x] == rank[y])
rank[y]++;
}
int find(int x) {
return x == p[x] ? x : (p[x] = find(p[x]));
}
void unin(int x, int y) {
link(find(x), find(y));
}
void compress() {
for (int i = 0; i < sz; i++)
find(i);
}
};
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
string s;
cin >> s;
s = " " + s;
UFS ufs(n);
for (int i = 1; i <= n; i ++) {
ufs.w[i] = s[i] == '0';
}
for (int i = 1; i <= n; i ++) {
ufs.unin(i, a[i]);
}
for (int i = 1; i <= n; i ++) {
cout << ufs.w[ufs.find(i)] << " \n"[i == n];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
E. Alternating String
思路
分类讨论。
对于 \(|S|\) 为偶数的情况,是不能使用第一个操作的,也就是说它的奇偶上的字符是固定了的,那么这个时候只要分别统计一下奇偶字符上的最多的个数即可,然后用 \(|S|\) 减去它们。
对于 \(|S|\) 为奇数的情况,这个时候删掉中间某个字符,那么这个字符往后的位置,奇偶位置的字符就会互换,所以我们可以维护一个前缀奇偶字符的个数和后缀奇偶字符的个数,然后去枚举中间的字符,那么将这个位置删掉后,前面奇数位上的字符就和后面偶数位上的字符对应,偶数位上反之,这个时候再去统计两数位上最多的字符数即可,然后用 \(|S|\) 减去它们。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n;
cin >> n;
string s;
cin >> s;
if (n & 1) {
int ans = 0;
vector pre(2, vector<int>(30));
auto suf = pre;
for (int i = 0; i < n; i ++) {
suf[i & 1][s[i] - 'a'] ++;
}
for (int i = 0; i < n; i ++) {
suf[i & 1][s[i] - 'a']--;
int res = 0;
for (int k = 0; k < 2; k ++) {
int mx = 0;
for (int j = 0; j < 26; j ++) {
int t = pre[k][j] + suf[k ^ 1][j];
if (t > mx) {
mx = t;
}
}
res += mx;
}
pre[i & 1][s[i] - 'a']++;
ans = max(ans, res);
}
ans = n - ans;
cout << ans << '\n';
} else {
int ans = 0;
vector res(2, vector<int>(30));
for (int i = 0; i < n; i ++) {
res[i & 1][s[i] - 'a'] ++;
}
for (int i = 0; i < 2; i ++) {
auto mx = *max_element(res[i].begin(), res[i].end());
ans += mx;
}
ans = n - ans;
cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
F. Sakurako's Box
思路
考虑 \(O(n^2)\) 做法,即令 \(S = \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^n{a_i\times a_j}\),则 \(ans=\frac{S}{\frac{n\times (n-1)}{2}}\) 。
考虑优化。
展开 \(S\) 得:\(a_1\times (a_2+a_3+\dots+a_n)+a_2\times(a_3+\dots+a_n)\dots\),其实就是枚举 \(i\) 的时候,加上 \(a_i\times (\sum\limits_{j=1}^n a_j-\sum\limits_{j=1}^ia_j)\),而 \(\sum\limits_{j=1}^na_j\) 就是总和,可以预处理,\(\sum\limits_{j=1}^ia_j\) 是当前位置的前 \(i\) 项和,可以在枚举 \(i\) 的时候 \(O(1)\) 计算,那么最后按照上述公式计算即可。
此处用了封装好的取模类重载了部分运算符,实际运算中还是需要用逆元等计算。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
//------取模机------//
using i64 = long long;
template<class T>
constexpr T power(T a, i64 b) {
T res {1};
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
} // 快速幂
constexpr i64 mul(i64 a, i64 b, i64 p) {
i64 res = a * b - i64(1.L * a * b / p) * p;
res %= p;
if (res < 0) {
res += p;
}
return res;
} // 取模乘
template<i64 P>
struct MInt {
i64 x;
constexpr MInt() : x {0} {}
constexpr MInt(i64 x) : x {norm(x % getMod())} {}
static i64 Mod;
constexpr static i64 getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(i64 Mod_) {
Mod = Mod_;
}//只有P<=0, setMod才生效
constexpr i64 norm(i64 x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr i64 val() const {
return x;
}
constexpr MInt operator-() const {
MInt res;
res.x = norm(getMod() - x);
return res;
}
constexpr MInt inv() const {
return power(*this, getMod() - 2);
}
constexpr MInt &operator*=(MInt rhs) & {
if (getMod() < (1ULL << 31)) {
x = x * rhs.x % int(getMod());
} else {
x = mul(x, rhs.x, getMod());
}
return *this;
}
constexpr MInt &operator+=(MInt rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MInt &operator-=(MInt rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MInt &operator/=(MInt rhs) & {
return *this *= rhs.inv();
}
friend constexpr MInt operator*(MInt lhs, MInt rhs) {
MInt res = lhs;
res *= rhs;
return res;
}
friend constexpr MInt operator+(MInt lhs, MInt rhs) {
MInt res = lhs;
res += rhs;
return res;
}
friend constexpr MInt operator-(MInt lhs, MInt rhs) {
MInt res = lhs;
res -= rhs;
return res;
}
friend constexpr MInt operator/(MInt lhs, MInt rhs) {
MInt res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
i64 v;
is >> v;
a = MInt(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
return os << a.val();
}
friend constexpr bool operator==(MInt lhs, MInt rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MInt lhs, MInt rhs) {
return lhs.val() != rhs.val();
}
friend constexpr bool operator<(MInt lhs, MInt rhs) {
return lhs.val() < rhs.val();
}
};
template<>
i64 MInt<0>::Mod = 1000000007; //只有P<=0, Mod才生效
constexpr int P = 1000000007; //在这设置要用的模数
using Z = MInt<P>;
//------取模机------//
void solve() {
int n;
cin >> n;
Z sum = 0;
vector<Z> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
sum += a[i];
}
Z res = 0, pre = 0;
for (int i = 1; i < n; i ++) {
pre += a[i];
res += a[i] * (sum - pre);
}
Z m = n;
Z ans = res / (m * (m - 1) / 2);
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
G. Sakurako's Task
思路
要使得 \(mex_k\) 最大,那么每个数都应该尽量地往前靠,那么最后数组应该变成什么样才是最佳的呢?
考虑题目中操作的性质,当 \(a_i\) 和 \(a_j\) 互质时,那么 \(a_i\) 和 \(a_j\) 一定可以互相减去或相加直到变成两个相邻的数;当 \(a_i\) 和 \(a_j\) 不互质时,那么 \(a_i\) 和 \(a_j\) 只能通过减去和相加变成两个相隔 \(\gcd(a_i,a_j)\) 的数。
根据以上结论,我们可以计算出 \(\gcd(a_1,a_2,\dots,a_n)=g\),那么最终的序列一定就只能变成 \(0,g,2g\dots\) 可以证明,无法有更佳答案。
简单证明一下,如果存在更佳答案,那么这个更佳答案一定存在两数之间差距小于 \(g\) ,而 \(\gcd(a_1,a_2,\dots,a_n)\le \gcd(a_i,a_j)(1\le i,j\le n,i\ne j)\),由第二段结论可知,两两最小的差距就是 \(\gcd(a_i,a_j)\),与此相矛盾,证毕。
得到最终序列后,就是枚举找一下第 \(k\) 个在什么位置即可,注意 \(n=1\) 的时候需要特判一下。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, k;
cin >> n >> k;
int g = 0;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++) {
cin >> a[i];
g = gcd(a[i], g);
}
if (n == 1) {
cout << (k <= a[1] ? k - 1 : k) << '\n';
return ;
}
for (int i = 1; i <= n; i ++) {
a[i] = (i - 1) * g;
}
for (int i = 2; i <= n; i ++) {
int x = a[i] - a[i - 1] - 1;
if (k > x) {
k -= x;
} else {
cout << a[i - 1] + k << '\n';
return ;
}
}
cout << a[n] + k << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
H. Sakurako's Test
思路
题目要求最小中位数,那么每个数都要尽可能地减去 \(x\),则这个操作实际上就是 \(a_i \bmod x\),也就是求所有数模 \(x\) 之后的中位数。
对于 \(a_i \bmod x\),可以看成 \(a_i=k\times x+b\),那如果将原数组按值域划分区域 \([0,x),[x,2x)\dots\),那么 \(a_i\) 就属于第 \(k+1\) 个区域,假设当前我们要判断 \(m\) 能不能作为中位数,那就要看 \(b\) 是否小于等于 \(m\) 从而判断 \(a_i\) 是否产生贡献即可。由于所有数都模了 \(x\),所以 \(0\le m< x\),所以我们可以首先做个值域前缀和,然后对每个区域通过作差计算在 \([l,l+m]\) 之中的数有多少,最后将这些贡献统计后再与 \(\frac{n}{2}+1\) 作比较判断 \(m\) 能不能作为中位数。
题目要求最小中位数,那么判断 \(m\) 是否可以作为中位数可以利用以上结论,又知道 \(m\in[0,x)\),那么二分即可。
找出所有可能的 \(x\) 的最小中位数之后,记录一下,那么对于询问就可以 \(O(1)\) 得出了。
代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
void solve() {
int n, q;
cin >> n >> q;
vector<int> pre(n + 1);
for (int i = 0; i < n; i ++) {
int x;
cin >> x;
pre[x] ++;
}
for (int i = 1; i <= n; i ++) {
pre[i] += pre[i - 1];
}
auto check = [&](int x, int m)->bool{
int res = 0;
for (int l = 0; l <= n; l += x) {
int r = min(l + m, n);
res += pre[r] - (l ? pre[l - 1] : 0);
}
return res >= n / 2 + 1;
};
vector<int> ans(n + 1);
for (int x = 1; x <= n; x ++) {
int l = 0, r = x;
while (l <= r) {
int mid = l + r >> 1;
if (check(x, mid)) {
r = mid - 1, ans[x] = mid;
} else {
l = mid + 1;
}
}
}
for (int i = 1; i <= q; i ++) {
int x;
cin >> x;
cout << ans[x] << " \n"[i == q];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}