Codeforces Round #781 (CF1665) 简要题解

CF1665A GCD vs LCM

题意

给定 \(n\),求正整数 \(a, b, c, d\) 使得 \(gcd(a, b) = lcm(c, d)\)\(a + b + c + d = n\)

输出任意一种即可

题解

显然,\(a = n - 3, b = c = d = 1\) 为一种合法方案,输出即可

代码

#include<bits/stdc++.h>
using namespace std;

int n, t;

int main()
{
    cin >> t;
    while(t--){
        cin >> n;
        cout << n - 3 << " " << 1 << " " << 1 << " " << 1 << endl;
    }
    return 0;
}

CF1665B Array Cloning Technique

题意

你有一个序列集合,初始时只有一个序列 \(a\),你可以进行下面两种操作

  1. 将序列集合中的一个序列复制一份并加入集合中

  2. 将两个元素交换(可以是不同序列中的元素)

问最少多少次操作可以使得集合中有一个只由一种元素构成的序列

题解

显然,最终得到的序列一定是由 \(a\) 序列中出现次数最多的元素构成的

不妨设 \(a\) 序列长度为 \(n\),其中出现次数最多的元素为 \(x\),出现了 \(k\)

显然贪心策略为

  1. 集合当前除了序列 \(a\) 还有别的序列有元素 \(x\),我们交换这个序列中的 \(x\)\(a\)

  2. 集合当前只有序列 \(a\) 有元素 \(x\),我们复制序列 \(a\)

显然,这可以使操作次数最少

代码

#include<bits/stdc++.h>
using namespace std;

const int N = 100010;

int n, t, a[N], mx;

map <int, int> mp;

int main()
{
    cin >> t;
    while(t--){
        cin >> n, mx = 0;
        for(int i = 1; i <= n; i++){
            cin >> a[i], mp[a[i]]++;
            if(mp[a[i]] > mx) mx = mp[a[i]];
        }
        int ans = 0, cur = n - mx;
        while(cur){
            ans++;
            if(mx >= cur) ans += cur, cur = 0;
            else cur -= mx, ans += mx, mx <<= 1;
        }
        cout << ans << endl;
        mp.clear();
    }
    return 0;
}

CF1665C Tree Infection

题意

给定一棵树,初始时每个节点都是健康的,现在不知道从哪里来了一种病毒,每一秒会发生如下两种事件

  1. 传染:若一个结点被感染,则他可以传染给它的一个兄弟结点

  2. 注射:可以指定一个结点,使其感染

假设你可以控制每一秒被传染和注射的点是谁,问最少几秒可以使整棵树被感染

题解

显然,对于每个结点,他的若干个子结点中我们必须注射一个(根结点也必须注射)

对于两个结点 \(u\)\(v\),如果 \(u\) 的子结点个数大于 \(v\) 的子结点个数,那么我们应该先注射 \(u\) 的子节点

当每个点都有子结点被注射时,我们再注射根结点

如果此时还有结点未被感染,假设 \(u\) 中未被感染的子结点数大于 \(v\) 中未被感染的子结点数,我们应该先注射 \(u\) 的子节点

用堆模拟即可

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 200010;
const int M = 400010;

int t, n, head[N], nxt[M], to[M], st[N], top, cnt, ex;
priority_queue <int> q;

inline void addedge(int u, int v)
{
    to[++cnt] = v;
    nxt[cnt] = head[u], head[u] = cnt;
}

bool cmp1(int x, int y) { return x > y; }

int main()
{
    cin >> t;
    while(t--){
        cin >> n, st[top = 1] = 1;
        for(int i = 2, u; i <= n; i++)
            cin >> u, addedge(u, i);
        for(int i = 1; i <= n; i++){
            int d = 0;
            for(int j = head[i]; j; j = nxt[j]) d++;
            if(d) st[++top] = d;
        }
        sort(st + 1, st + top + 1, cmp1);
        for(int i = 1; i <= top; i++)
            if(st[i] > top - i + 1) q.push(st[i] - top + i - 1);
        if(!q.empty()) for(ex = 1; ex < q.top(); ex++){
            int u = q.top(); q.pop();
            q.push(u - 1);
            if(ex >= q.top()) break;
        }
        cout << top + ex << endl;
        for(int i = 1; i <= n; i++) head[i] = st[i] = to[i] = nxt[i] = 0;
        n = top = cnt = ex = 0;
        while(!q.empty()) q.pop();
    }
    return 0;
}

CF1665D GCD Guess

题意

交互题

有一个数 \(x\),你可以给出正整数 \(a, b\) 询问 \(gcd(x + a, x + b)\)

你需要在 \(30\) 次询问内猜出 \(x\)

其中 \(x \le 10^9\)

题解

观察发现 \(x \le 10^9\),次数在 \(30\) 次以内

显然,这里需要用到一个常识: \(\lceil \log_2 10^9 \rceil = 30\)

也就是说 \(x\) 最多有 \(30\) 个二进制位

每次询问确定一个二进制位即可

假设当前是 \(2^k\) 位,前 \(k - 1\) 位是 \(y\),那么我们询问 \(2^{k - 1} - y + 2^{k+1}, 2^{k-1} - y\),如果答案是 \(2^{k+1}\),那么 \(2^k\) 位就是 \(0\),否则就是 \(1\)

由于 \(a, b\) 需要是正整数,那么我们猜 \(x + 1\),最后再减一即可

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

LL t, cur, now;

LL que(LL a, LL b)
{
    cout << "? " << a << " " << b << endl;
    LL res = 0;
    return cin >> res, res;
}

void answer(LL x) { cout << "! " << x << endl; }

int main()
{
    cin >>t;
    while(t--){
        for(cur = 1, now = 2; now <= (1ll << 30); now <<= 1)
            if(que(cur + now, cur) != now) cur += (now >> 1);
        answer((1ll << 30) - cur);
    }
    return 0;
}

CF1665E MinimizOR

题意

给定序列 \(a\),多次询问区间最小或

\(a_i < 2^{30}\)

题解

由于 \(a_i < 2^{30}\) 那么最小或只会在最小的 \(31\) 个数字中产生

证明:

考虑数学归纳法,证明当 \(a_i < 2^k\) 时,最小或在最小的 \(k + 1\) 个数中产生

  1. \(a_i < 2^1\),结论显然成立

  2. \(a_i < 2^k\) 时结论成立,当 \(a_i < 2^{k+1}\) 时,设 \(2^k\)\(0\) 的数有 \(t\) 个,分类讨论如下

    1. \(t > 2\),则答案中 \(2^k\) 位为 \(0\)

      1. \(t > k + 1\),则答案会在最小的 \(k + 1\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生

      2. \(t \le k + 1\),则答案会在最小的 \(t\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生

    2. \(t \le 1\) 则答案中 \(2^k\) 位为 \(1\)

      1. \(t = 0\) 则答案会在最小的 \(k + 1\) 个数中产生,自然也会在最小的 \(k + 2\) 个数中产生

      2. \(t = 1\) 则答案会在前 \(k-1\) 位中最小的 \(k + 1\) 个数中产生,也肯定会在最小的 \(k + 2\) 个数中产生

也就是对于每个询问找到区间最小的 \(31\) 个数后暴力判断即可,使用线段树,复杂度 \(O(n \log^2 a)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
const int inf = 1 << 30;

int n, T, q, a[N];

struct node{
    int mn[32];
    node operator +(const node a)const{
        int t1 = 0, t2 = 0;
        node res;
        for(int i = 0; i < 31; i++)
            res.mn[i] = mn[t1] < a.mn[t2] ? mn[t1++] : a.mn[t2++];
        return res;
    }

    void create(int x){
        mn[0] = x;
        for(int i = 1; i < 31; i++) mn[i] = inf;
    }

    int getans(){
        int ans = inf;
        for(int i = 1; i < 31; i++)
            for(int j = 0; j < i; j++) ans = min(ans, mn[i] | mn[j]);
        return ans;
    }
}t[N << 2];

void build(int l, int r, int x)
{
    if(l == r) return t[x].create(a[l]), void();
    int mid = (l + r) >> 1;
    build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
    t[x] = t[x << 1] + t[x << 1 | 1];
}

node que(int l, int r, int x, int L, int R)
{
    if(L <= l && r <= R) return t[x];
    node res; res.create(inf);
    int mid = (l + r) >> 1;
    if(L <= mid) res = res + que(l, mid, x << 1, L, R);
    if(R > mid) res = res + que(mid + 1, r, x << 1 | 1, L, R);
    return res;
}

int main()
{
    cin >> T;
    while(T--){
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> a[i];
        build(1, n, 1);
        cin >> q;
        while(q--){
            int l, r;
            cin >> l >> r;
            node cur = que(1, n, 1, l, r);
            cout << cur.getans() << endl;
        }
    }
    return 0;
}
posted @ 2022-04-09 17:16  sgweo8ys  阅读(107)  评论(0编辑  收藏  举报