Codeforces Round 960 (Div. 2) A~E

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 1in ),使得 a i ≥ m x a_{i} \geq mx aimx ,并将 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 1in 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

  1. 设置 s u m : = s u m + ∑ i = 1 n a i sum := sum + \sum_{i=1}^{n} a_i sum:=sum+i=1nai
  2. b b b 成为大小为 n n n 的数组。将所有 1 ≤ i ≤ n 1 \le i \le n 1in 设置为 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 1in 设置为 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 1xn ) 来向陪审团询问。接下来,当鼹鼠在子树 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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值