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

  1. 一直问一个位置 \(p\),直至 \(x=p\)
  2. \(2\times 未知位置数\) 的时间问出其他位置与 \(a_p\) 的大小关系,这样也就知道了 \(a_p\) 的值
  3. 类似快速排序,分治处理比 \(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

posted @ 2024-02-02 11:43  Bellala  阅读(9)  评论(0编辑  收藏  举报