A.Brick Wall(模拟)
题意:
砖块是大小为 1 × k 1\times k 1×k的条状物体,可以水平或垂直放置,其中 k k k是至少为 2 2 2的任意数字。
给你一面大小为 n × m n\times m n×m的墙,要求用砖块填满这堵墙。即在 n × m n\times m n×m的矩形内放置砖块,所有砖都水平或垂直地放置,不超出矩形的边界,并且 n × m n\times m n×m矩形的每个单元格上正好有一块砖。 n n n是矩形 n × m n\times m n×m的高度, m m m是宽度。
注意,在同一面墙中可能存在 k k k值不同的砖块。
墙的稳定性是水平砖块数与垂直砖块数之差。
注意,如果使用了 0 0 0块水平砖和 2 2 2块垂直砖,那么稳定性将是 − 2 -2 −2,而不是 2 2 2。
求大小为 n × m n\times m n×m的墙的最大稳定性是多少?
可以保证,在题目的限制条件下,存在一面这样的墙。
分析:
想实现最高的稳定性,就横着放就可以了,因为长度必须大于等于 2 2 2,所以全放 1 × 2 1\times 2 1×2的横砖,有塞不满的可以不用管,把最后的 2 2 2拉长填满剩下的位置即可,此时就是稳定性最大的状态。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
int ans = m / 2 * n;
cout << ans << endl;
}
return 0;
}
B.Minimize Inversions(模拟)
题意:
给你两个长度为 n n n的排列 a a a和 b b b。排列是由从 1 1 1到 n n n的 n n n个元素组成的数组,其中所有元素都是不同的。例如,数组[ 2 , 1 , 3 2,1,3 2,1,3]是一个排列,但是[ 0 , 1 0,1 0,1]和[ 1 , 3 , 1 1,3,1 1,3,1]不是。
你可以随机选择两个索引 i i i和 j j j,然后同时把 a i a_i ai和 a j a_j aj以及 b i b_i bi和 b j b_j bj互换。
你讨厌倒置,所以你想尽量减少两个排列中倒置的总数。
排列组合 p p p中的倒置是指一对索引 ( i , j ) (i,j) (i,j),即 i < j i\lt j i<j和 p i > p j p_i \gt p_j pi>pj。例如,如果有 p = [ 3 , 1 , 4 , 2 , 5 ] p=[3,1,4,2,5] p=[3,1,4,2,5],那么其中就有 3 3 3个倒置数(这些索引分别是 ( 1 , 2 ) (1,2) (1,2)、 ( 1 , 4 ) (1,4) (1,4)和 ( 3 , 4 ) (3,4) (3,4))。
分析:
因为交换会影响到两个序列,所以要上下都要考虑进去,并且把一上一下作为一个整体来看待。
把 a 1 a_1 a1和 b 1 b_1 b1看做一个整体, a 2 a_2 a2和 b 2 b_2 b2看做一个整体,分析可以得出三种情况。
-
a 1 < a 2 a_1\lt a_2 a1<a2, b 1 < b 2 b_1\lt b_2 b1<b2。这种情况 a 1 a_1 a1和 b 1 b_1 b1不会产生逆序对,所以要放 a 1 a_1 a1和 b 1 b_1 b1在左边。
-
a 1 > a 2 a_1\gt a_2 a1>a2, b 1 > b 2 b_1\gt b_2 b1>b2。这种情况 a 1 a_1 a1和 b 1 b_1 b1会产生两个逆序对, a 1 a_1 a1和 b 1 b_1 b1必须交换到 a 2 a_2 a2和 b 2 b_2 b2的后面。
-
a 1 > a 2 a_1\gt a_2 a1>a2, b 1 < b 2 b_1\lt b_2 b1<b2或 a 1 < a 2 a_1\lt a_2 a1<a2, b 1 > b 2 b_1\gt b_2 b1>b2。这种情况会产生一个逆序对,那就把他们按照升序放在一起,遇到情况 1 1 1和 2 2 2再做改变。
分析完之后发现,这三个条件使用 s o r t sort sort排序后进行遍历即可得出结果。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
bool cmp(pii &p, pii &q) {
return p.first + p.second < q.first + q.second;
}
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
pii arr[n];
for (int i = 0; i < n; i++)
cin >> arr[i].first;
for (int i = 0; i < n; i++)
cin >> arr[i].second;
sort(arr, arr + n, cmp);
for (int i = 0; i < n; i++)
cout << arr[i].first << " ";
cout << endl;
for (int i = 0; i < n; i++)
cout << arr[i].second << " ";
cout << endl;
}
return 0;
}
C.XOR-distance(数学)
题意:
给你整数 a a a、 b b b、 r r r。求所有 0 ≤ x ≤ r 0 \leq x \leq r 0≤x≤r中 ∣ ( a ⊕ x ) − ( b ⊕ x ) ∣ |({a\oplus x})-({b\oplus x})| ∣(a⊕x)−(b⊕x)∣的最小值。
⊕ \oplus ⊕是按位异或运算, ∣ y ∣ |y| ∣y∣是 y y y的绝对值。
分析:
按位来看、并且先不看绝对值:如果
a
、
b
a、b
a、b相等,那么
x
x
x无论取什么值都无法对结果造成影响,反之,可以通过
x
x
x的取
1
1
1或是
0
0
0来影响正负。
解,从最高的不同的位看起,我们让那一位是
1
1
1的那个数为大的数,另一个数为小的数。让这一位的
x
x
x为
0
0
0,如果为
1
1
1会使得
x
x
x更大,在后续的影响中不如填
0
0
0优。接着后续的低位
a
,
b
a,b
a,b如果出现
0
,
1
0,1
0,1不同的情况,如果小的那个数为
1
1
1则不用处理;否则,需要让
x
x
x的那一位为
1
1
1,让其变为
1
1
1,注意处理
x
x
x不够大的情况。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, m, r;
void solve() {
cin >> n >> m >> r;
LL res = 0, now = 0;
int t = -1, idx = 0;
for (int i = 63; i >= 0; i--) {
if ((n >> i & 1) != (m >> i & 1)) {
t = i;
if (n >> i & 1)
idx = 2;
else
idx = 1;
break;
}
}
if (t == -1)
cout << 0 << endl;
else {
res += 1ll << t;
for (int i = t - 1; i >= 0; i--) {
if ((n >> i & 1) != (m >> i & 1)) {
if (idx == 1) {
if (!(n >> i & 1)) {
if (now + (1ll << i) <= r) {
now += (1ll << i);
res -= (1ll << i);
} else
res += (1ll << i);
} else res -= (1ll << i);
} else {
if (!(m >> i & 1)) {
if (now + (1ll << i) <= r) {
now += (1ll << i);
res -= (1ll << i);
} else
res += (1ll << i);
} else
res -= (1ll << i);
}
}
}
cout << res << endl;
}
}
int main() {
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
D.Blocking Elements (二分+dp)
题意:
给出一个长度为 n n n的序列 a a a,可以从中选择 m m m个元素,这 m m m个元素将序列分割成 m + 1 m+1 m+1段。记序列的代价为选出的 m m m个元素的权值和与分割出的 m + 1 m+1 m+1段中权值和最大的段两者取最大值。现在请你最小化序列的代价。
分析:
我们首先考虑二分答案,判断序列的代价能否小于等于 x x x。 d p [ i ] dp[i] dp[i]表示最后一个分割点在 i i i时的最小花费,可以考虑在 n + 1 n+1 n+1设置一个虚拟点,用于边界处理, c h e c k check check函数的判断条件为 d p [ n + 1 ] ≤ x dp[n+1] \le x dp[n+1]≤x。利用双指针+前缀和确定 j j j的位置,使得 j − i − 1 j-i-1 j−i−1这段的花费不大于 x x x,转移方程为 d p [ i ] = m i n ( d p [ k ] ) + a [ i ] ; dp[i]=min(dp[k])+a[i]; dp[i]=min(dp[k])+a[i];。发现可以用单调队列进行优化,每次取队首即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int MAXN = 2e5 + 5;
LL a[MAXN];
LL dp[MAXN];
LL sum[MAXN];
int n;
LL check(LL x) {
deque<int> dq;
dq.push_back(0);
for (int i = 0; i <= n + 1; i++)
dp[i] = 0;
for (int i = 1; i <= n + 1; i++) {
while (!dq.empty() && sum[i - 1] - sum[dq.front()] > x) {
dq.pop_front();
}
dp[i] = dp[dq.front()] + a[i];
while (!dq.empty() && dp[dq.back()] >= dp[i])
dq.pop_back();
dq.push_back(i);
}
return dp[n + 1] <= x;
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
a[0] = a[n + 1] = 0;
sum[0] = sum[n + 1] = 0;
for (int i = 1; i <= n + 1; i++) {
sum[i] = sum[i - 1] + a[i];
}
LL l = 0, r = 1e18;
LL ans = 0;
while (l <= r) {
LL mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << endl;
}
}
E.ace5 and Task Order (交互)
题意:
这是一道交互题,你需要去猜测一个排列,一开始系统会确定一个基准数 x x x,你可以进行如下询问: ? i ? i ?i,系统会进行如下回答:
- > > >表示 a i > x a_i>x ai>x,同时将 x + + x++ x++
- < < <表示 a i < x a_i<x ai<x,同时将 x − − x-- x−−
- = = =表示 a i = x a_i=x ai=x, x x x保持不变
你需要在不超过 40 n 40n 40n次的询问中,确定排列的值,并输出这个排列。
分析:
题意可以概括成选择一个元素,让其他元素和这个元素比较,并对数组排序,容易联想到快速排序。我们首先要确定基准元素,不停询问直到得到 = = =。接着和选择的元素进行比较,假设当前选择元素为 x x x,将剩余所有数字和 x x x进行比较,如果有 s i z e 1 size1 size1个数字小于 x x x,那么它在答案的位置就是 l + s i z e 1 − 1 l+size1-1 l+size1−1。通过递归可以得到 p o s pos pos数组。本题为了防止快排退化,需要随机打乱数组。
代码:
#include <bits/stdc++.h>
using namespace std;
char query(int x) {
cout << "? " << x << endl;
char c;
cin >> c;
return c;
}
char cmp(int x, int y) {
char c = query(y);
query(x);
return c;
};
void solve(vector<int> &tmp, int l, int r) {
if (l >= r)
return;
int x = tmp[(l + r) / 2];
while (query(x) != '=');
vector<int> tmp1, tmp2;
for (int i = l; i <= r; i++) {
char c = cmp(x, tmp[i]);
if (c == '<') {
tmp1.push_back(tmp[i]);
} else if (c == '>') {
tmp2.push_back(tmp[i]);
}
}
int size1 = tmp1.size();
copy(tmp1.begin(), tmp1.end(), tmp.begin() + l);
copy(tmp2.begin(), tmp2.end(), tmp.begin() + l + size1 + 1);
tmp[l + size1] = x;
solve(tmp, l, l + size1 - 1);
solve(tmp, l + size1 + 1, r);
return;
}
int main() {
int t = 1;
cin >> t;
while (t--) {
int n;
cin >> n;
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
vector<int> pos(n);
for (int i = 0; i < n; i++)
pos[i] = i + 1;
shuffle(pos.begin(), pos.end(), rnd);
solve(pos, 0, n - 1);
vector<int> ans(n + 1);
for (int i = 0; i < n; i++) {
ans[pos[i]] = i;
}
cout << "! ";
for (int i = 1; i <= n; i++)
cout << ans[i] + 1 << " ";
cout << endl;
}
return 0;
}
F. Caterpillar on a Tree (图论)
题意:
给一棵有根树,你需要从根节点开始访问树上的所有结点,你有两种移动方式:
- 沿着一条边到相邻节点,消耗 1 m i n 1min 1min。
- 传送到根,不会消耗时间,不会访问新节点。最多使用 k k k次。
询问访问完树上的所有结点至少需要多少时间。
分析:
如果去掉第二个操作,那么答案为 2 × ( n − 1 ) − m a x ( d e p i ) 2 \times (n-1) - max(dep_i) 2×(n−1)−max(depi)。加上第二个操作之后,我们先贪心地思考,发现我们总是要访问完一个子树内的所有点才可能会传送回根节点。那么对于每一个点先将其所有子节点按照深度最深的点从小到大排序,再按照这个顺序访问,则最后只要考虑欧拉序中第一次出现的位置相邻的两个点之间的影响。这样的位置是 O ( n ) O(n) O(n)级别,全部找出来之后,贪心选择最大的 k k k个。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 2e5 + 10;
int n, k;
vector<int> g[MAXN];
int chose[MAXN], son[MAXN];
vector<int> seq;
int vis[MAXN], dep[MAXN];
void dfs(int u) {
for (auto c: g[u]) {
dep[c] = dep[u] + 1;
dfs(c);
if (chose[c] + 1 > chose[u])
chose[u] = chose[c] + 1, son[u] = c;
}
}
void dfs2(int u) {
seq.push_back(u);
for (auto c: g[u]) {
if (c == son[u])
continue;
dfs2(c);
seq.push_back(u);
}
if (son[u])
dfs2(son[u]), seq.push_back(u);
}
int main() {
cin >> n >> k;
for (int i = 2; i <= n; i++) {
int x;
cin >> x, g[x].push_back(i);
}
dfs(1);
dfs2(1);
vector<int> pos;
for (int i = 0; i < seq.size(); i++)
if (!vis[seq[i]])
vis[seq[i]] = 1, pos.push_back(i);
vector<LL> dis;
int ans = seq.size() - 1 - chose[1];
for (int i = 1; i < pos.size(); i++)
dis.push_back(pos[i] - pos[i - 1] - dep[seq[pos[i]]]);
sort(dis.begin(), dis.end(), greater<int>());
for (int i = 0; i < min((LL) k, (LL) dis.size()); i++)
ans -= max(dis[i], 0ll);
cout << ans << endl;
return 0;
}
G. Permutation of Given (打表+循环节)
题意:
给出序列的长度 n n n,询问是否能构造出一个只包括非零数字长度为 n n n的序列,使得序列中的每个元素都满足如下条件:
- 将序列中的每个元素都替换成邻居元素的和,第一个和最后一个元素只有一个邻居。并得到一个新序列,这个序列是原始序列的一个排列。
分析:
通过打表发现, n = 3 , 5 n=3,5 n=3,5是无解的,偶数情况取数范围可以固定在 [ − 2 , 2 ] [-2,2] [−2,2],奇数情况取数范围可以固定在[-3,3]。并发现无论偶数还是奇数循环节长度都为 6 6 6。
代码:
#include <bits/stdc++.h>
using namespace std;
int a[10]{-2, -1, 1, -1, 1, 2};
int b[10]{2, 3, -3, -1, 1, -2};
int main() {
int n;
cin >> n;
if (n % 2 == 0) {
cout << "YES" << endl;
for (int i = 0; i < n; i++) {
cout << a[i % 6] << " ";
}
cout << endl;
return 0;
}
if (n <= 5) {
cout << "NO" << endl;
return 0;
}
vector<int> ans{-3, -3, 2, 1, -1, 1, -2};
for (int i = 0; i < n - 7; i++) {
ans.push_back(b[i % 6]);
}
cout << "YES" << endl;
for (int i = 0; i < ans.size(); i++) {
cout << ans[i] << " ";
}
cout << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。