Codeforces Round 1024 (Div. 2)
A. Dinner Time
题意:判断能不能构造一个长度为\(n\)的数组,使得数组和为\(m\),且每个长度为\(p\)的子数组的和都为\(q\)。
往前缀和去想。那么就是\(sum[i] - sum[i - p] = q\)。发现如果\(n\)是\(p\)的倍数,那么\(sum[n] = sum[n - m] + q = sum[n - m - m] + q + q ... = sum[0] + q \times \frac{n}{p}\)。\(sum[0]\)就是\(0\)。所以此时\(m\)必须等于\(q \times \frac{n}{p}\)。
否则如果\(n\)不是\(p\)的倍数,发现这些限制好像没用了,直接猜测是\(YES\)。
点击查看代码
void solve() {
int n, m, p, q;
std::cin >> n >> m >> p >> q;
if (n % p != 0) {
std::cout << "YES" << "\n";
} else {
if (m == n / p * q) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
}
B. The Picky Cat
题意:给你数组\(a\),你可以对任意一个位置乘上\(-1\)。求能不能使得\(a_1\)是数组的中位数。
分\(a_1\)乘不乘\(-1\)的情况讨论。把乘不乘\(-1\)都小于\(a_1\)和乘不乘\(-1\)都大于\(a_1\)的数找出来。那么其它数可以随意选择是大于\(a_1\)还是小于\(a_1\)。那么只要这些固定大于或小于\(a_1\)的数不会把\(a_1\)挤出中位数这个位置就行了。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
auto check = [&]() -> bool {
int cnt = 0, cnt1 = 0;
for (int i = 1; i < n; ++ i) {
cnt += a[i] > a[0] && -a[i] > a[0];
cnt1 += a[i] < a[0] && -a[i] < a[0];
}
return cnt <= n - (n + 1) / 2 && cnt1 <= (n + 1) / 2 - 1;
};
if (check()) {
std::cout << "YES\n";
} else {
a[0] = -a[0];
if (check()) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
}
C. Mex in the Grid
题意:构造一个包含\([0, n^2 - 1]\)每个数恰好一次的\(n\times n\)的矩阵。使得所有子数组的\(mex\)之和最大。
猜猜题。
猜测是把\(0\)放在最中间,然后按照其它数从小到大蛇形走位围着中间走。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector a(n, std::vector<int>(n, -1));
int x = (n - 1) / 2, y = (n - 1) / 2;
int cnt = 0;
a[x][y] = cnt ++;
const int dx[4] = {0, 1, 0, -1};
const int dy[4] = {1, 0, -1, 0};
int step = 1;
int dir = 0;
while (cnt < n * n) {
for (int t = 0; t < 2 && cnt < n * n; ++ t) {
for (int s = 0; s < step && cnt < n * n; ++ s) {
x += dx[dir];
y += dy[dir];
if (0 <= x && x < n && 0 <= y && y < n) {
a[x][y] = cnt++;
}
}
dir = (dir + 1) % 4;
}
step ++;
}
for (int i = 0; i < n; ++ i){
for (int j = 0; j < n; ++ j){
std::cout << a[i][j] << " \n"[j == n - 1];
}
}
}
D. Quartet Swapping
题意:给你一个排列\(a\),每次选择\(i\),使得\(a_i, a_{i+1}, a_{i+2}, a_{i+3}\)变为\(a_{i+2}, a_{i+3}, a_i, a_{i+1}\)。求最小的字典序。
首先容易发现不会改变这个数位置的奇偶性,这提示我们可以分奇偶位置做。
但是否可以彻底分开,两部分任意发挥呢?继续观察发现,分析操作对奇数位置组成的序列和偶数位置组成的序列的逆序对的影响,发现一次操作,两个序列的逆序队改变的奇偶性相同。那么这意味着不管两个序列这么交换,它们原先的逆序对奇偶的关系不会改变,也就是如果一开始两个序列的逆序对奇偶性相同,那么操作后也相同,不同操作后也不同。那么我们求出两个序列的逆序对,如果相同,直接分别排序然后合并就行。如果不同,那么枚举交换哪个序列的最后两个使得答案更优就行。
点击查看代码
template <class T>
struct Fenwick {
int n;
std::vector<T> tr;
Fenwick(int _n) {
init(_n);
}
void init(int _n) {
n = _n;
tr.assign(_n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] = tr[i] + v;
}
}
T query(int x) {
T res{};
for (int i = x; i; i -= i & -i) {
res = res + tr[i];
}
return res;
}
T sum(int l, int r) {
return query(r) - query(l - 1);
}
};
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
-- a[i];
}
std::vector<int> odd, even;
for (int i = 0; i < n; ++ i) {
if (i & 1) {
even.push_back(a[i]);
} else {
odd.push_back(a[i]);
}
}
auto get = [&](std::vector<int> & a, std::vector<int> & b) -> int {
int m = (int)a.size();
std::vector<int> p(n);
for (int i = 0; i < m; ++ i) {
p[b[i]] = i;
}
Fenwick<int> tr(m + 1);
i64 res = 0;
for (int i = m - 1; i >= 0; -- i) {
res += tr.query(p[a[i]] + 1);
tr.add(p[a[i]] + 1, 1);
}
return res & 1;
};
auto merge = [&](std::vector<int> & a, std::vector<int> & b) -> std::vector<int> {
std::vector<int> res(n);
for (int i = 0, j = 0, k = 0; i < n; ++ i) {
if (i & 1) {
res[i] = b[k ++ ];
} else {
res[i] = a[j ++ ];
}
}
return res;
};
auto odd1 = odd;
auto even1 = even;
std::ranges::sort(odd1);
std::ranges::sort(even1);
std::vector<int> ans;
if (get(odd, odd1) == get(even, even1)) {
ans = merge(odd1, even1);
} else {
auto odd2 = odd1;
auto even2 = even1;
int x = odd2.size(), y = even2.size();
std::swap(odd2[x - 1], odd2[x - 2]);
std::swap(even2[y - 1], even2[y - 2]);
auto ans1 = merge(odd2, even1);
auto ans2 = merge(odd1, even2);
ans = ans1 < ans2 ? ans1 : ans2;
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] + 1 << " \n"[i == n - 1];
}
}