Codeforces Round 922 (Div. 2)
https://codeforces.com/contest/1918
题目很有意思。A~D vp中过了,但是太太太慢,亟须复健。E赛后过的,交互题真是难调!F看题解过的
A. Brick Wall *800 用砖头砌墙
有形状 \(1\times k\) 的水平砖和形状 \(k \times 1\) 的竖直砖,要不重不漏地铺满 \(n\times m\) 的区域,问水平砖数量与竖直砖数量之差的最大值。任意两块砖的 \(k\) 都不必相同。
既然不要求砖的规格一样,那就用水平砖直接铺满就行。
cout << n * (m / 2);
B. Minimize Inversions *900
给定两个 \(n\) 的排列,可以任意次交换两个下标,交换时两个数组的对应位置也分别交换,即交换 \(a_i,a_j\) 同时交换 \(b_i,b_j\)。最小化 \(a[]\) 的逆序对数量 \(+\) \(b[]\) 的逆序对数量
类似贪心问题之排序不等式。但实际上单纯地把 \(a[]\) 排个序就行了,因为这样保证了 \(a_i<a_j\),而无论 \(b_i>b_j\) 还是 \(b_i<b_j\),交换 \(i,j\) 都不会让答案更优
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
int n;
cin >> n;
vector<pair<int, int>> p(n);
for (int i = 0; i < n; i++) cin >> p[i].first;
for (int i = 0; i < n; i++) cin >> p[i].second;
sort(p.begin(), p.end());
for (int i = 0; i < n; i++) cout << p[i].first << " \n"[i == n - 1];
for (int i = 0; i < n; i++) cout << p[i].second << " \n"[i == n - 1];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
C. XOR-distance *1400
给定 \(a,b,r\),问 \(|(a\oplus x) - (b\oplus x)|, x\in [0,r]\) 的最小值
1e4组数据,\(0\le a,b,r \le 10^{18}\)
按位考虑,如果a和b某位相同,x取啥都不会对答案产生贡献,为了让x小一点,x这位取0。在a,b相异的最高位h上,x取啥都会给答案带来 \(2^h\),后面的位加起来也没有这个 \(2^h\) 大,后面的位只能尽量稍微缩小一下答案
我们希望 x 的第h位取0以使x较小,同时希望 \((a\oplus x) - (b\oplus x)\ge 0\) 以去掉绝对值符号,因此不妨假设 \(a\ge b\)
然后按位考虑,贪心搞搞即可
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void sol() {
ll a, b, r;
cin >> a >> b >> r;
if (a < b) swap(a, b);
ll x = 0, ans = 0;
for (int i = 61, f = false; ~i; i--) {
if ((a >> i & 1) == (b >> i & 1)) continue; //相同位不用管
if (!f) { //a,b相异的第一位
ans += 1ll << i;
f = true;
} else if (a >> i & 1) {
if (x + (1ll << i) <= r) {
x += 1ll << i;
ans -= 1ll << i;
} else {
ans += 1ll << i;
}
} else {
ans -= 1ll << i;
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
D. Blocking Elements *1900 二分答案+dp+单调队列优化
给定正整数数组 \(a[]\),可选择任意个下标 \(b_1 < b_2 < \cdots <b_k\),把数组分成一些子段 \([1,b_1-1],[b_1+1,b_2-1],\cdots\)
代价取下面两个东西的最大值:
- \(a_{b_1}+a_{b_2}+\cdots +a_{b_k}\)
- 上述子段和的最大值
问最小代价是多少。
\(n\le 1e5, 1\le a_i\le 10^9\)
二分答案,dp判断。\(dp[i]\) 表示考虑 \(1\sim i\),要选 \(i\) ,每个子段和都 \(\le mid\),\(\sum a_{b_i}\) 最小是多少。
不妨假设 \(a_0=a_{n+1}=0\),这俩都要选
\(dp_i\) 能被满足 \(a_{j+1}+\cdots +a_{i-1}\le mid\) 的 \(dp_j\) 更新。我们要找其中最小的
用set或者单调队列维护即可
感觉很适合作为单调队列优化dp的入门
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5;
ll n, a[N], s[N], f[N];
int q[N], hh, tt;
bool ok(ll mid) {
hh = 1; tt = 0;
q[++tt] = 0;
for (int i = 1; i <= n + 1; i++) {
while (hh <= tt && s[i - 1] - s[q[hh]] > mid) hh++;
if (hh > tt) return false;
f[i] = f[q[hh]] + a[i];
while (hh <= tt && f[i] <= f[q[tt]]) tt--;
q[++tt] = i;
}
return f[n + 1] <= mid;
}
void sol() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
s[n + 1] = s[n];
a[n + 1] = 0;
ll l = 0, r = 1e15, ans;
while (l <= r) {
ll mid = l + r >> 1;
if (ok(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
E. ace5 and Task Order *2200 快速排序真神奇
交互题。有未知的排列 \(a[]\) 和数 \(x\),输入 \(n\)。每次询问一个位置 \(i\),若 \(a_i>x\),回答
">"
,并且 \(x\) 增大 \(1\);若 \(a_i<x\),回答"<"
,并且 \(x\) 减小 \(1\);若相等,回答"="
,\(x\) 不变。请在 \(40n\) 次询问内猜出原排列。\(1\le x\le n\le 2000\)
- 一直问一个位置 \(p\),直至 \(x=p\)
- 用 \(2\times 未知位置数\) 的时间问出其他位置与 \(a_p\) 的大小关系,这样也就知道了 \(a_p\) 的值
- 类似快速排序,分治处理比 \(a_p\) 小/大的位置
1的次数会不会太多?应该不会吧。。画一下分治的过程,应有左子区间的分治点≤父区间的划分点≤右子区间的分治点,考虑递归过程的dfs树,x在同一层内的移动距离应该 \(\le n\)
当然快排为了不被卡,应该先打乱数组或者每次取随机点。询问次数 \(O(nlogn)\)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2010;
random_device rd;
mt19937 gen(rd());
int a[N];
void f(vector<int> &pos, int bond) {
if (pos.empty()) return;
if (pos.size() == 1) {
a[pos[0]] = bond;
return;
}
vector<int> L, R;
int p = gen() % (int)pos.size();
for (char ch = '?'; ch != '='; ) {
cout << "? " << pos[p] << endl;
cin >> ch;
}
for (int i = 0; i < (int)pos.size(); i++) {
if (i == p) continue;
cout << "? " << pos[i] << endl;
char ch; cin >> ch;
(ch == '<' ? L : R).push_back(pos[i]);
cout << "? " << pos[p] << endl; //把x变回来
cin >> ch;
}
a[pos[p]] = bond + (int)L.size();
f(L, bond); f(R, a[pos[p]] + 1);
}
void sol() {
int n;
cin >> n;
vector<int> pos(n);
iota(pos.begin(), pos.end(), 1);
f(pos, 1);
cout << "! ";
for (int i = 1; i <= n; i++) cout << a[i] << ' ';
cout << endl;
}
int main() {
// ios::sync_with_stdio(0);
// cin.tie(0);
int T; cin >> T; while (T--)
sol();
}
F. Caterpillar on a Tree *2500 树上毛毛虫
从根出发,每次走到邻点要花 1 时间,可以不超过 k 次从任意点立即回到根,问访问完整棵树的最少时间。
n 2e5, k 1e9
若某点被访问,则它的所有祖先都被访问过,因此访问所有的叶子就够了。从非叶点跳回根肯定不优,应该在访问某叶子后直接跳回根
我们可以把整个过程视为访问每一个叶子的过程,即不断从一个叶子走到另一个叶子的过程。答案由 根到叶子的路径、叶子到叶子的路径、叶子跳回根 这三种过程组成。访问整棵树的最后一个叶子后直接结束即可,不必再回到根
整个过程长这样:\(root \to leaf_1 \to leaf_2 \to \cdots \to leaf_i \to leaf_j \to \cdots \to leaf_{k}\)
把根到叶子的路径用跳根替换掉是没有必要的,我们要做的是把一些从叶子到相邻叶子的路径用跳根替换掉,即把 \(leaf_i \to leaf_j\) 用 \(root \to leaf_j\) 替换掉,也就是把 \(leaf_i\to u \to leaf_j\) 用 \(root\to u \to leaf_j\) 替换掉,其中 \(u\) 为 \(\text{lca}(leaf_i,leaf_j)\)。节省的时间为 \(\Delta_{ans}\) 两个叶子被访问的时间戳之差减去 \(leaf_j\) 的深度
为了使答案更优,我们希望 \(leaf_i\to u\) 尽可能长,因此应该最后访问最深的儿子
注意到叶子数 \(\le n\),因此把所有 \(\Delta_{ans}\) 记下来,取最好的 \(k\) 个即可
官方题解还有个很长的证明,懒得看了qaq
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 4e5 + 5;
int n, k;
vector<int> G[N];
int dep[N], son[N]; //最深儿子
int dfs(int u) { //返回最深叶子的深度
int ans = dep[u];
for (int v : G[u]) {
dep[v] = dep[u] + 1;
int res = dfs(v);
if (res > ans) ans = res, son[u] = v;
}
for (auto it = G[u].begin(); it != G[u].end(); it++) {
if (*it == son[u]) {
swap(*it, G[u].back());
break;
}
}
return ans;
}
vector<int> ans;
int idx, las = 1; //上一个叶子的访问时间
void euler(int u) {
if (G[u].empty()) { //遇叶子,尝试把上一个叶子→u的路径用跳到根来替换
ans.push_back(-(idx - las) + dep[u]);
las = idx;
}
else for (int v : G[u]) {
++idx; //访问v
euler(v);
++idx; //回到u
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> k;
for (int i = 2; i <= n; i++) {
int p; cin >> p;
G[p].push_back(i);
}
dfs(1);
euler(1);
sort(ans.begin(), ans.end());
while ((int)ans.size() > k || !ans.empty() && ans.back() >= 0) ans.pop_back();
cout << las + accumulate(ans.begin(), ans.end(), 0);
}
G. Permutation of Given *2700 神仙构造题
给定 \(n\),构造数组 \(a[]\),使得数组 \(b[],b_i=a_{i-1}+a_{i+1}\) 是 \(a[]\) 的重排
打表找规律:https://zhuanlan.zhihu.com/p/680665380
官解,想不到的构造:https://www.cnblogs.com/cpchenpi/p/-/CF1918-solutions