Codeforces Round #746 (Div. 2) A - E

https://codeforces.com/contest/1592

A

给你若干个武器的攻击力,不能用相同的武器攻击敌人,问最少需要攻击多少次才能消灭敌人

  • 显然先用伤害最大的武器攻击,然后用第二大的武器,一直这样进行下去即可,先把两个武器看做一个整体考虑
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t;
    cin >> t;
    while(t--){
        int n, h;
        cin >> n >> h;
        vector<int> a(n);
        for(int i=0;i<n;i++){
            cin >> a[i];
        }
        sort(a.begin(), a.end());
        int k = h / (a[n - 1] + a[n - 2]);
        h %= (a[n - 1] + a[n - 2]);
        if(h == 0) cout << k * 2 << '\n';
        else{
            h -= a[n - 1];
            if(h <= 0) cout << k * 2 + 1 << '\n';
            else cout << k * 2 + 2 << '\n';
        }
    }
    return 0;
}

B

  • 如果某个位置错了,那他必须能够移动到最左面或者最右面,否则就无法调整,一旦能移动到最左面或者最右面,就有可能得到调整,在后续的判断中进一步验证中间的位置能否得到调整
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t, n, x;
    cin >> t;
    while(t--){
        cin >> n >> x;
        vector<int> a(n);
        vector<int> b(n);
        for(int i=0;i<n;i++){
            cin >> a[i];
            b[i] = a[i];
        }sort(a.begin(), a.end());
        bool f = true;
        for(int i=0;i<n;i++){
            if(a[i] != b[i] && x > i && x > (n - 1 - i)){
                f = false;
            }
        }
        cout << (f ? "YES" : "NO") << '\n';
    }
    return 0;
}

C

给你一颗树,给定每个节点的点权,问能否通过删除某些边使得产生的若干子树内部异或和相等,删除的边的数量在 1 1 1 k − 1 k-1 k1之间(闭区间)

  • 显然如果初始异或和就为 0 0 0,那么根据异或的性质,删除任意一条边均可达到目的;如果初始异或和不是 0 0 0,假设为 p p p,如果我们可以分出 n u m num num棵子树,那么我们必然可以分出 n u m − 2 num-2 num2棵子树,因为 x ⊕ x ⊕ x = x x\oplus x\oplus x=x xxx=x,所以我们找子树异或和为 p p p的子树看有多少棵,如果数量 ≥ \geq 2 2 2,那么就一定有 ≥ 3 \geq3 3棵树的内部异或和 = p =p =p,分成三棵树即可
#include <bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 100;
struct Edge{
    int next;
    int to;
    int val;
}edge[MAXN];
int head[MAXN];
int a[MAXN];
int cnt;
int p, f;
void Add_Edge(int u, int v, int w){
    edge[cnt].next = head[u];
    edge[cnt].to = v;
    edge[cnt].val = w;
    head[u] = cnt++;
}
int dfs(int u, int fa){
    int ans = a[u];
    for(int i=head[u];~i;i=edge[i].next){
        int v = edge[i].to;
        if(v == fa) continue;
        int t = dfs(v, u);
        if(t == p){
            f += 1;
        }else{
            ans ^= t;
        }
    }
    return ans;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t, n, k, u, v;
    cin >> t;
    while(t--){
        cin >> n >> k;
        p = f = cnt = 0;
        for(int i=1;i<=n;i++){
            cin >> a[i];
            p ^= a[i];
        }
        memset(head, -1, sizeof head);
        for(int i=1;i<n;i++){
            cin >> u >> v;
            Add_Edge(u, v, 1);
            Add_Edge(v, u, 1);
        }
        if(p == 0){
            cout << "YES\n";
            continue;
        }else{
            if(k >= 3){
                dfs(1, -1);
                if(f >= 2){
                    cout << "YES\n";
                    continue;
                }
            }
        }
        cout << "NO\n";
    }
    return 0;
}

D

给你一棵树,现在要进行十二次以内的询问,每次询问任意两个节点之间的最大边权,求出树上最大边权

  • 看到 n ≤ 2000 n\leq 2000 n2000 12 12 12,能够想到应该是二分,问题是现在如果树上二分,不好办,考虑如何将树上问题转化为一条链呢?考虑 d f s dfs dfs序或者 b f s bfs bfs序,我使用 b f s bfs bfs序,选择孩子节点最多的那个点作为树根,这可以使用逆拓扑序确定最后的序列,然后在这个序列上进行二分,每次都询问经过根节点的边权最大值,不出现在这一侧就出现在那一侧,不断二分直到锁定节点,最终的答案就是这个节点和它的父亲节点之间的边权
#include <bits/stdc++.h>

using namespace std;

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, u, v;
    cin >> n;
    vector<vector<int>> g(n);
    for(int i=1;i<n;i++){
        cin >> u >> v;
        u -= 1;
        v -= 1;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    vector<int> d(n);
    queue<int> q;
    for(int i=0;i<n;i++){
        d[i] = g[i].size();
        if(d[i] == 1) q.push(i);
    }
    vector<int> t;
    while(!q.empty()){
        u = q.front();
        q.pop();
        t.push_back(u);
        for(auto i : g[u]){
            d[i] -= 1;
            if(d[i] == 1){
                q.push(i);
            }
        }
    }
    reverse(t.begin(), t.end());
    v = t[0];
    q.push(v);
    vector<int> vs(n, -1);
    while(!q.empty()){
        u = q.front();
        q.pop();
        for(auto i : g[u]){
            if(vs[i] != u){
                vs[i] = u;
                q.push(i);
            }
        }
    }
    cout << "? " << n;
    for(int i=1;i<=n;i++){
        cout << ' ' << i;
    }cout << endl;
    int mx;
    cin >> mx;
    int l = 1;
    int r = n;
    while(r - l > 1){
        int mid = ((r - l) >> 1) + l;
        cout << "? " << mid;
        for(int i=0;i<mid;i++){
            cout << ' ' << t[i] + 1;
        }cout << endl;
        int val;
        cin >> val;
        if(mx == val){
            r = mid;
        }else{
            l = mid;
        }
    }
    int a = t[r - 1];
    int b = vs[a];
    cout << "! " << a + 1 << ' ' << b + 1;
    return 0;
}

E

给定一个序列,问其中 [ l , r ] [l,r] [l,r]区间 & > ⊕ \&\gt \oplus &>的最长区间有多长,如果不存在就输出 0 0 0

  • 首先考虑什么时候 & > ⊕ \&\gt \oplus &>,因为与比较特殊,必须都是 1 1 1才能是 1 1 1,否则就是 0 0 0,所以对于一个奇数区间段,无论是哪种情况异或结果都是 1 1 1,也就是说一个区间长度为奇数的区间是不可能成立的
  • 如果是一个偶数长度的区间,我们来进行这样的考虑,枚举每一个二进制位,对 a a a数组计算这个二进制位之前的数字的前缀异或和 p r e [ i ] pre[i] pre[i],如果当前二进制位可能合法,那么就必须在之前找到一个 p r e [ i ] pre[i] pre[i]使得和当前相等因为如果有 p r e [ l − 1 ] = p r e [ r ] pre[l-1]= pre[r] pre[l1]=pre[r],那么就会有 [ l , r ] [l,r] [l,r]这个区间内异或和为 0 0 0
  • 现在我们使用两个指针, l , r l,r l,r分别表示往左走第一个当前二进制位是 0 0 0的下标,当前位置下标,也就是说这两个位置的当前二进制位一个是 0 0 0,另一个是 1 1 1,更新答案的时候只需要将 a n s ans ans r − l r-l rl取最大即可
  • 那么具体该如何操作呢?我们开个桶,像这样gp_hash_table<int, vector<int>> vs[2];一个哈希表,表示奇数位或者偶数位前缀异或和是多少的一个集合,首先 f o r for for一圈把哈希表填好,之后我们在哈希表里面二分查找有没有当前合法的 l l l,如果有,就更新答案,如果 r r r位置上的二进制值是 0 0 0,那么就更新 l l l为当前的 r r r
#include <bits/stdc++.h>
#include <bits/extc++.h>
using namespace std;
using namespace __gnu_pbds;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for(int i=1;i<=n;i++){
        cin >> a[i];
    }
    int ans = 0;
    for(int k=0;k<=20;k++){ 
        vector<int> pre(n + 1);
        gp_hash_table<int, vector<int>> vs[2];
        vs[0][0].push_back(0);
        for(int i=1;i<=n;i++){
            pre[i] = (pre[i - 1] ^ (a[i] >> k));
            vs[i & 1][pre[i]].push_back(i);
        }
        int l = 0;
        for(int r=1;r<=n;r++){
            if((a[r] >> k) & 1){
                auto &v = vs[r & 1][pre[r]];
                auto it = lower_bound(v.begin(), v.end(), l);
                if(it != v.end()) ans = max(ans, r - *it);
            }else{
                l = r;
            }
        }
    }
    cout << ans;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值