A. Submission Bait (思维)
题意:
A l i c e Alice Alice 和 B o b Bob Bob 正在玩一个数组 a a a 中大小为 n n n 的游戏。
他们轮流进行操作, A l i c e Alice Alice先开始。无法操作的玩家将输掉。首先,将变量 m x mx mx 设置为 0 0 0 。
在一次操作中,玩家可以执行以下操作:
- 选择一个索引 i i i ( 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n ),使得 a i ≥ m x a_{i} \geq mx ai≥mx ,并将 m x mx mx 设置为 a i a_{i} ai 。然后,将 a i a_{i} ai 设置为 0 0 0 。
确定 A l i c e Alice Alice是否有获胜策略。
分析:
考虑最大值的奇偶性,如果有奇数个最大值,那么先手赢。如果是偶数个最大值,那么就要考虑剩下的数字的奇偶性。如果有一个值有奇数个,那么先手赢,否则后手赢
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t = 1;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
a[x]++;
}
int flag = 0;
for (int i = n; i; i--) {
if (a[i] & 1) {
flag = 1;
break;
}
}
if (flag)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
B.Array Craft (思维)
题意:
对于大小为 m m m 的数组 b b b ,我们定义:
- b b b 的最大前缀位置是满足 b 1 + … + b i = max j = 1 m ( b 1 + … + b j ) b_1+\ldots+b_i=\max_{j=1}^{m}(b_1+\ldots+b_j) b1+…+bi=maxj=1m(b1+…+bj) 的最小索引 i i i ;
- b b b 的最大后缀位置是满足 b i + … + b m = max j = 1 m ( b j + … + b m ) b_i+\ldots+b_m=\max_{j=1}^{m}(b_j+\ldots+b_m) bi+…+bm=maxj=1m(bj+…+bm) 的最大索引 i i i 。
给出三个整数 n n n 、 x x x 和 y y y ( x > y x > y x>y )。构造一个大小为 n n n 的数组 a a a ,满足:
- 对于所有 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n , a i a_i ai 为 1 1 1 或 − 1 -1 −1 ;
- a a a 的最大前缀位置为 x x x ;
- a a a 的最大后缀位置为 y y y 。
如果有多个数组满足条件,则输出任意一个。可以证明,在给定条件下,这样的数组始终存在。
分析:
我们让 y y y的左侧 − 1 , 1 -1,1 −1,1交替出现,在区间 [ y , x ] [y,x] [y,x]全选择 1 1 1, x x x的右侧也是 − 1 , 1 -1,1 −1,1交替出现即可完成构造。
代码:
#include <bits/stdc++.h>
using namespace std;
int main() {
int t = 1;
cin >> t;
while (t--) {
int n, x, y;
cin >> n >> x >> y;
vector<int> ans(n + 1, 1);
for (int i = y - 1; i >= 1; i--) {
ans[i] = -ans[i + 1];
}
for (int i = x + 1; i <= n; i++) {
ans[i] = -ans[i - 1];
}
for (int i = 1; i <= n; i++) {
cout << ans[i] << " ";
}
cout << endl;
}
return 0;
}
C.Mad MAD Sum (思维)
题意:
我们将数组中的 M A D {MAD} MAD (最大重复数)定义为数组中至少出现两次的最大数字。具体来说,如果没有至少出现两次的数字,则 M A D {MAD} MAD 的值是 0 0 0 。
例如, M A D ( [ 1 , 2 , 1 ] ) = 1 {MAD}([1, 2, 1]) = 1 MAD([1,2,1])=1 、 M A D ( [ 2 , 2 , 3 , 3 ] ) = 3 {MAD}([2, 2, 3, 3]) = 3 MAD([2,2,3,3])=3 、 M A D ( [ 1 , 2 , 3 , 4 ] ) = 0 {MAD}([1, 2, 3, 4]) = 0 MAD([1,2,3,4])=0 。
给定一个大小为 n n n 的数组 a a a 。最初,将变量 s u m sum sum 设置为 0 0 0 。
以下过程将以顺序循环执行,直到 a a a 中的所有数字都变为 0 0 0 :
- 设置 s u m : = s u m + ∑ i = 1 n a i sum := sum + \sum_{i=1}^{n} a_i sum:=sum+∑i=1nai ;
- 让 b b b 成为大小为 n n n 的数组。将所有 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n 设置为 b i : = M A D ( [ a 1 , a 2 , … , a i ] ) b_i :=\ {MAD}([a_1, a_2, \ldots, a_i]) bi:= MAD([a1,a2,…,ai]) ,然后将所有 1 ≤ i ≤ n 1 \le i \le n 1≤i≤n 设置为 a i : = b i a_i := b_i ai:=bi 。
求出经过此处理后的 s u m sum sum 的值。
分析:
发现从第二次操作开始,数列一定为单调递增,且除了最大的权值外每种权值至少出现
2
2
2次,且每次操作对数列的影响均为使数列整体右移一位,删去最后一个数并在前面补
0
0
0。
于是我们先暴力模拟两轮记录贡献和,然后求得每种权值的出现次数,再降序枚举每种权值(也即每轮删数的顺序),统计删去该权值的各轮的贡献之和即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
int t = 1;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<LL> a(n), b(n);
LL ans = 0;
map<LL, LL> mp;
LL mx = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
ans += a[i];
mp[a[i]]++;
if (mp[a[i]] >= 2 && a[i] > mx) {
mx = a[i];
}
b[i] = mx;
}
mp.clear();
mx = 0;
for (int i = 0; i < n; i++) {
ans += b[i];
mp[b[i]]++;
if (mp[b[i]] >= 2 && b[i] > mx)
mx = b[i];
a[i] = mx;
}
for (int i = 0; i < n; i++)
ans += a[i] * (n - i);
cout << ans << endl;
}
return 0;
}
D.Grid Puzzle (dp)
题意:
您将获得一个大小为 n n n 的数组 a a a 。
有一个 n × n n \times n n×n 网格。在 i i i 行中,前 a i a_i ai 个单元格为黑色,其他单元格为白色。换句话说,请注意 ( i , j ) (i,j) (i,j) 是 i i i行和 j j j列中的单元格,单元格 ( i , 1 ) , ( i , 2 ) , … , ( i , a i ) (i,1), (i,2), \ldots, (i,a_i) (i,1),(i,2),…,(i,ai) 为黑色,单元格 ( i , a i + 1 ) , … , ( i , n ) (i,a_i+1), \ldots, (i,n) (i,ai+1),…,(i,n) 为白色。
你可以按任意顺序多次执行以下操作:
- 将 2 × 2 2 \times 2 2×2 子网格染成白色;
- 将整行染成白色。请注意,你不能 将整列染成白色。
找出将所有单元格染成白色的最少操作次数。
分析:
我们发现,在每一行最多用一次 2 × 2 2 \times 2 2×2的操作,因为超过 1 1 1次,不如直接两次操作涂完两列。那么每一行最多被两个 2 × 2 2 \times 2 2×2的操作影响(前一行和本行),并且可以看出,这个操作要么用在第 1 , 2 1,2 1,2列,要么用在 3 , 4 3,4 3,4列。所以我们维护以下 3 3 3种 d p dp dp值。
- 前一行是涂一整行。
- 前一行在第 1 , 2 1,2 1,2列使用了 2 × 2 2 \times 2 2×2操作。
- 前一行在第 3 , 4 3,4 3,4列使用了 2 × 2 2 \times 2 2×2操作。
进行转移即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
int t = 1;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int dp[3]{0, INF, INF}, tmp[3];
for (int i = 0; i < n; i++) {
memset(tmp, 0x3f, sizeof tmp);
tmp[0] = *min_element(dp, dp + 3) + (a[i] > 0);
if (a[i] <= 2) {
tmp[0] = min(tmp[0], dp[1]);
tmp[1] = min(tmp[1], dp[0] + 1);
}
if (a[i] <= 4) {
tmp[2] = min(tmp[2], dp[1] + 1);
tmp[1] = min(tmp[1], dp[2] + 1);
}
swap(dp, tmp);
}
cout << *min_element(dp, dp + 3) << endl;
}
return 0;
}
E2.Catch the Mole(Hard Version) (交互)
题意:
这是一个交互题。
给你一个节点数为
n
n
n 的树,节点
1
1
1 是它的根节点。
其中一个节点中隐藏着一只鼹鼠。要找到它的位置,你可以选择一个整数
x
x
x (
1
≤
x
≤
n
1 \le x \le n
1≤x≤n ) 来向陪审团询问。接下来,当鼹鼠在子树
x
x
x 中时,陪审团将返回
1
1
1 。否则,法官将返回
0
0
0 。如果法官返回
0
0
0 并且鼹鼠不在根节点
1
1
1 中,鼹鼠将移动到它当前所在节点的父节点。
最多使用
160
160
160 次操作来找到鼹鼠所在的当前节点。
分析:
我们每次找出当前询问点子树的重心,这样就能一次排除尽量多的点,但如果出现类似菊花图之类的数据,是难以找到一个较为平衡的点,但要卡掉上述过程的图恰好需要满足点的分布较为均衡,那么此时子树的深度一定不大,很容易将目标点直接跳到根节点。
所以我们可以先在子树内每次选择重心缩小范围,最后留出一部分询问在当前点到根的路径上确定点的位置即可,因为可能这个点之前还在我们确定的子树里,几次操作后又跑到外面去了,所以要最后确定一下位置。由于这部分是一条链,因此可以二分处理,实测可以在
160
160
160的限制内通过此题.
代码:
#include <bits/stdc++.h>
using namespace std;
inline bool qry(int x) {
cout << "? " << x << endl;
int s;
cin >> s;
return s;
}
vector<int> s[5005], e[5005];
int sz[5005], f[5005], le, rt;
void dfs(int x, int fa) {
sz[x] = 1;
f[x] = fa;
for (auto u: e[x]) {
if (u == fa)
continue;
s[x].push_back(u);
dfs(u, x);
sz[x] += sz[u];
}
if (e[x].size() == 1 && x != 1)
le = x;
if (sz[x] >= 71 && !rt) {
if (qry(x)) {
rt = x;
}
sz[x] = 0;
}
}
void solve() {
int n;
cin >> n;
rt = 0;
for (int i = 1; i <= n; i++) {
e[i].clear();
s[i].clear();
}
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
if (rt == 0)
rt = 1;
for (int i = 1; i < 71; i++) {
if (qry(le)) {
cout << "! " << le << "\n";
return;
}
}
vector<int> v;
while (rt)
v.push_back(rt), rt = f[rt];
int l = 0, r = v.size() - 1;
reverse(v.begin(), v.end());
while (l < r) {
int mid = l + r + 1 >> 1;
if (qry(v[mid]))
l = mid;
else {
r = mid - 1;
l--;
r--;
}
if (l < 0)
l = 0;
}
cout << "! " << v[l] << endl;
}
int main() {
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。