Codeforces Round 992(Div.2) A~G

A.Brick Wall(模拟)

题意:

砖块是大小为1×k1\times k1×k的条状物体,可以水平或垂直放置,其中kkk是至少为222的任意数字。

给你一面大小为n×mn\times mn×m的墙,要求用砖块填满这堵墙。即在n×mn\times mn×m的矩形内放置砖块,所有砖都水平或垂直地放置,不超出矩形的边界,并且n×mn\times mn×m矩形的每个单元格上正好有一块砖。nnn是矩形n×mn\times mn×m的高度,mmm是宽度。

注意,在同一面墙中可能存在kkk值不同的砖块。

墙的稳定性是水平砖块数与垂直砖块数之差。

注意,如果使用了000块水平砖和222块垂直砖,那么稳定性将是−2-22,而不是222

求大小为n×mn\times mn×m的墙的最大稳定性是多少?

可以保证,在题目的限制条件下,存在一面这样的墙。

分析:

想实现最高的稳定性,就横着放就可以了,因为长度必须大于等于222,所以全放1×21\times 21×2的横砖,有塞不满的可以不用管,把最后的222拉长填满剩下的位置即可,此时就是稳定性最大的状态。

代码:

#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(模拟)

题意:

给你两个长度为nnn的排列aaabbb。排列是由从111nnnnnn个元素组成的数组,其中所有元素都是不同的。例如,数组[2,1,32,1,32,1,3]是一个排列,但是[0,10,10,1]和[1,3,11,3,11,3,1]不是。

你可以随机选择两个索引iiijjj,然后同时把aia_iaiaja_jaj以及bib_ibibjb_jbj互换。

你讨厌倒置,所以你想尽量减少两个排列中倒置的总数。

排列组合ppp中的倒置是指一对索引(i,j)(i,j)(i,j),即i<ji\lt ji<jpi>pjp_i \gt p_jpi>pj。例如,如果有p=[3,1,4,2,5]p=[3,1,4,2,5]p=[3,1,4,2,5],那么其中就有333个倒置数(这些索引分别是(1,2)(1,2)(1,2)(1,4)(1,4)(1,4)(3,4)(3,4)(3,4))。

分析:

因为交换会影响到两个序列,所以要上下都要考虑进去,并且把一上一下作为一个整体来看待。

a1a_1a1b1b_1b1看做一个整体,a2a_2a2b2b_2b2看做一个整体,分析可以得出三种情况。

  1. a1<a2a_1\lt a_2a1<a2b1<b2b_1\lt b_2b1<b2。这种情况a1a_1a1b1b_1b1不会产生逆序对,所以要放a1a_1a1b1b_1b1在左边。

  2. a1>a2a_1\gt a_2a1>a2b1>b2b_1\gt b_2b1>b2。这种情况a1a_1a1b1b_1b1会产生两个逆序对,a1a_1a1b1b_1b1必须交换到a2a_2a2b2b_2b2的后面。

  3. a1>a2a_1\gt a_2a1>a2b1<b2b_1\lt b_2b1<b2a1<a2a_1\lt a_2a1<a2b1>b2b_1\gt b_2b1>b2。这种情况会产生一个逆序对,那就把他们按照升序放在一起,遇到情况111222再做改变。

分析完之后发现,这三个条件使用sortsortsort排序后进行遍历即可得出结果。

代码:

#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(数学)

题意:

给你整数aaabbbrrr。求所有0≤x≤r0 \leq x \leq r0xr∣(a⊕x)−(b⊕x)∣|({a\oplus x})-({b\oplus x})|(ax)(bx)的最小值。

⊕\oplus是按位异或运算,∣y∣|y|yyyy的绝对值。

分析:

按位来看、并且先不看绝对值:如果a、ba、bab相等,那么xxx无论取什么值都无法对结果造成影响,反之,可以通过xxx的取111或是000来影响正负。
解,从最高的不同的位看起,我们让那一位是111的那个数为大的数,另一个数为小的数。让这一位的xxx000,如果为111会使得xxx更大,在后续的影响中不如填000优。接着后续的低位a,ba,ba,b如果出现0,10,10,1不同的情况,如果小的那个数为111则不用处理;否则,需要让xxx的那一位为111,让其变为111,注意处理xxx不够大的情况。

代码:

#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)

题意:

给出一个长度为nnn的序列aaa,可以从中选择mmm个元素,这mmm个元素将序列分割成m+1m+1m+1段。记序列的代价为选出的mmm个元素的权值和与分割出的m+1m+1m+1段中权值和最大的段两者取最大值。现在请你最小化序列的代价。

分析:

我们首先考虑二分答案,判断序列的代价能否小于等于xxxdp[i]dp[i]dp[i]表示最后一个分割点在iii时的最小花费,可以考虑在n+1n+1n+1设置一个虚拟点,用于边界处理,checkcheckcheck函数的判断条件为dp[n+1]≤xdp[n+1] \le xdp[n+1]x。利用双指针+前缀和确定jjj的位置,使得j−i−1j-i-1ji1这段的花费不大于 xxx,转移方程为dp[i]=min(dp[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 (交互)

题意:

这是一道交互题,你需要去猜测一个排列,一开始系统会确定一个基准数xxx,你可以进行如下询问:?i? i?i,系统会进行如下回答:

  • >>>表示ai>xa_i>xai>x,同时将x++x++x++
  • <<<表示ai<xa_i<xai<x,同时将x−−x--x
  • ===表示ai=xa_i=xai=xxxx保持不变

你需要在不超过40n40n40n次的询问中,确定排列的值,并输出这个排列。

分析:

题意可以概括成选择一个元素,让其他元素和这个元素比较,并对数组排序,容易联想到快速排序。我们首先要确定基准元素,不停询问直到得到===。接着和选择的元素进行比较,假设当前选择元素为xxx,将剩余所有数字和xxx进行比较,如果有size1size1size1个数字小于xxx,那么它在答案的位置就是l+size1−1l+size1-1l+size11。通过递归可以得到pospospos数组。本题为了防止快排退化,需要随机打乱数组。

代码:

#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 (图论)

题意:

给一棵有根树,你需要从根节点开始访问树上的所有结点,你有两种移动方式:

  • 沿着一条边到相邻节点,消耗1min1min1min
  • 传送到根,不会消耗时间,不会访问新节点。最多使用kkk次。

询问访问完树上的所有结点至少需要多少时间。

分析:

如果去掉第二个操作,那么答案为2×(n−1)−max(depi)2 \times (n-1) - max(dep_i)2×(n1)max(depi)。加上第二个操作之后,我们先贪心地思考,发现我们总是要访问完一个子树内的所有点才可能会传送回根节点。那么对于每一个点先将其所有子节点按照深度最深的点从小到大排序,再按照这个顺序访问,则最后只要考虑欧拉序中第一次出现的位置相邻的两个点之间的影响。这样的位置是O(n)O(n)O(n)级别,全部找出来之后,贪心选择最大的kkk个。

代码:

#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 (打表+循环节)

题意:

给出序列的长度nnn,询问是否能构造出一个只包括非零数字长度为nnn的序列,使得序列中的每个元素都满足如下条件:

  • 将序列中的每个元素都替换成邻居元素的和,第一个和最后一个元素只有一个邻居。并得到一个新序列,这个序列是原始序列的一个排列。

分析:

通过打表发现,n=3,5n=3,5n=3,5是无解的,偶数情况取数范围可以固定在[−2,2][-2,2][2,2],奇数情况取数范围可以固定在[-3,3]。并发现无论偶数还是奇数循环节长度都为666

代码:

#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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值