Codeforces Round #835 (Div. 4) A - F

提示:此文代码中出现的一些宏定义

#define pb push_back
#define int long long
#define all(x) (x).begin(), (x).end()
#define yon(x) cout << ((x) ? "Yes" : "No") << endl;
#define readin(i, n, v) for(int o = (i); o < (int)(n) + (i); o ++ ) cin >> (v)[o];
#define wtline(i, n, v) for(int o = (i); o < (int)(n) + (i); o ++ ) cout << (v)[o] << " \n"[o == (int)(n) + (i) - 1];

A. Medium Number

题意

给你三个数a, b, c。找出他们的中位数。

解法

读入三个数,排序,输出中间的数。

Code 复杂度O(1)
void solve() {
    int a[3];
    readin(0, 3, a);
    sort(a, a + 3);
    cout << a[1] << endl;    
}

B. Atilla’s Favorite Problem

题意

给你一个字符串s,问s中出现的最大字符是第几个英文字符(1-index)

解法

读入字符串,求出最大字符为 s m a x s_{max} smax,输出 s m a x − ′ a ′ + 1 s_{max} - 'a' + 1 smaxa+1即可。

Code 复杂度O(N)
void solve() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    cout << *max_element(s.begin(), s.end()) - 'a' + 1 << endl;
}

C. Advantage

题意

有n个参赛者,每个人都想知道自己和除了自己之外的最厉害的参赛者的能力之差

解法

对于不是最厉害的参赛者而言,答案就是自己的能力减去最厉害参赛者的能力

对于最厉害的参赛者来说,除了他之外最厉害的参赛者就是能力第二大的参赛者,他的答案就是自己的能力减去第二大参赛者的能力

Code 复杂度O(NLogN)
void solve() {
    int n;
    cin >> n;
    vector<int> v(n);
    readin(0, n, v);
    vector<int> c(v);
    sort(all(c));
    for(int i = 0; i < n; i ++ ) {
        if(v[i] != c[n - 1]) {
            v[i] = v[i] - c[n - 1];
        }else {
            v[i] = v[i] - c[n - 2];
        }
    }
    wtline(0, n, v);
}

D. Challenging Valleys

题意

给你一个数组,你需要判定这个数组是否是**” v a l l e y valley valley“**的

如果一个数组存在仅有一个区间 a [ l . . . r ] a[l...r] a[l...r]满足如下四点,则是**” v a l l e y valley valley“**的(0-index)

  1. $0\leq l\leq r\leq n - 1 $
  2. a l = a l + 1 = a l + 2 = . . . = a r a_l=a_{l+1}=a_{l+2}=...=a_r al=al+1=al+2=...=ar
  3. l = 0   ∣ ∣   a l < a l − 1 l=0\ ||\ a_l<a_{l-1} l=0  al<al1
  4. r = n − 1   ∣ ∣   a r < a r + 1 r=n-1\ ||\ a_r<a_{r+1} r=n1  ar<ar+1
解法

容易发现,我们可以通过第二点来将整个数组分为多个段,那么我们只需check一下每个段是否符合上述要求,并记录下符合要求段的个数为 c n t cnt cnt,最后判断 c n t = = 1 cnt==1 cnt==1即可

Code 复杂度O(N)
void solve() {
    int n;
    cin >> n;
    vector<int> v(n);
    readin(0, n, v);
    int f = 0;
    for(int i = 0; i < n; i ++ ) {
        int r = i;
        while(r < n && v[r] == v[i]) r ++;
        if((i == 0 || v[i] < v[i - 1]) && (r == n || v[r - 1] < v[r])) {
            f ++;
        }
        i = r - 1;
    }
    yon(f == 1);
 
}

E. Binary Inversions

题意

给你一个只由0,1组成的二进制数组,你可以选择最多一位将其反转(0变1,1变0),问你能得到的最大逆序对数量是多少。

解法

我们先考虑对于位置 i i i 产生的逆序对贡献如何计算:

​ 假设 a i = 0 a_i=0 ai=0,那么位置 i i i 只能和它前面的每个1构成逆序对,记数量为 c n t 1 cnt1 cnt1

​ 假设 a i = 1 a_i=1 ai=1,那么位置 i i i 只能和它后面的每个0构成逆序对,记数量为 c n t 0 cnt0 cnt0

我们可以发现,将0变成1,带来的贡献是 c n t 0 − c n t 1 cnt0-cnt1 cnt0cnt1,反之,带来的贡献是 c n t 1 − c n t 0 cnt1-cnt0 cnt1cnt0

至此,该问题已经可以解决了,通过前缀和来求位置 i i i 前面的1和后面的0即可,只要对所有位置求一下改变贡献的最大值就好。

Code(solution1) 复杂度O(N)
void solve() {
    int n;
    cin >> n;
    vector<int> v(n + 1), pre(n + 1);
    readin(1, n, v);
    for(int i = 1; i <= n; i ++ ) {
        pre[i] = pre[i - 1] + v[i];
    }
    int ans = 0, mx = 0;
    for(int i = 1; i <= n; i ++ ) {
        if(v[i]) ans += n - i - pre[n] + pre[i];
 
        if(v[i]) {
            mx = max(mx, pre[i - 1] - (n - i - pre[n] + pre[i]));
        }else {
            mx = max(mx, n - i - pre[n] + pre[i] - pre[i - 1]);
        }
    }
    cout << ans + mx << endl;
}
EX

可以更深入的思考一下,对于数字0,我们修改哪个位置是最优贡献?对于数字1,我们修改哪个位置是最优贡献?

显然,对于数字0,我们希望它后面的0尽量多,前面的1尽量少,这个位置就显而易见了,即第一个0出现的地方,对于数字1刚好反过来,是最后一个1出现的地方。

F. Quests

题意

有n个任务,如果你完成第i个任务,你将获得 a i a_i ai 金币,每天你最多只能做一个任务。

但是有一个限制k,如果你今天做了第i个任务,那么k天内,你就不能再做这一个任务

更形式的表达是:对于k,i,如果你第p天做了任务i,那么你下次可以做任务i是在第p+k+1天

现在给你n,c,d。n是任务个数,c是你需要赚取的金币数,d是最大天数,问在d天内,你能赚取至少c金币的最大限制k是多少

解法

我们简单思考一下,如果限制k=m,并且此时我们刚好能完成任务(k=m+1时就不行了),那么当限制越来越小时,我们也一定能完成任务。

如此,将限制分为了两部分,一部分是 [ 0 , m ] [0,m] [0,m](可以完成的限制),另一部分是 [ m + 1 , + ∞ ) [m+1,+\infty) [m+1,+)(不能完成的限制)

那么我们会发现,这个是符合二分性质的,我们可以二分这个m的位置在哪里去check它。

注意特判题目提出的两种情况:k可以无限大或者k不存在

Code 复杂度O(max(N, Log1e9))
void solve() {
    int n, c, d;
    cin >> n >> c >> d;
    vector<int> v(n), p(n + 1);
    readin(0, n, v);
    sort(all(v), greater<int>());
    for(int i = 1; i <= n; i ++ ) {
        p[i] = v[i - 1];
        p[i] += p[i - 1];
    }
    if(d * v[0] < c) {
        cout << "Impossible" << endl;
        return ;
    }
    if(p[min(d, n)] >= c) {
        cout << "Infinity" << endl;
        return ;
    }
    auto check = [&](int m) -> bool {
        int clc = d / (m + 1);// 在限制k=m时,d天内,我们可以重复取最大的min(m + 1, n)个数 d/(m+1) 次
        int lst = d % (m + 1);// 剩下的天数,我们可以取最大的min(n, lst)个数字
        return p[min(m + 1, n)] * clc + p[min(n, lst)] >= c;
    };
    int l = 0, r = 1e9;
    while(l < r) {
        int m = l + r + 1 >> 1;
        if(check(m)) {
            l = m;
        }else {
            r = m - 1;
        }
    }
    cout << l << endl;
}

G. SlavicG’s Favorite Problem

题意

已知一个n个顶点的边带权树,(树是一个无向无环的连通图)

你需要找到一条路从a到b,使得你走过的路径上的边权异或和为0

你可以使用最多一次转移操作:从当前的顶点跳转到任意顶点(不包括b)处

问你是否可以找到这样一条路径

分析

我们会发现,跳转后可以从任何点走到b,跳转前可以从走到除了b子树的所有点(如果直接能走进b,那么特判一下)
所以,实质上找的是a能走到的所有异或值和b能走到的所有异或值是否有相等的。

Code 复杂度O(NLogN)
vector<pii> G[maxn];
int A[maxn], B[maxn];
int n, a, b, ok = 0;

void dfs1(int u, int fa, int sxor) {
    if(ok) return ;
    A[u] = sxor;
    for(auto [ne, w] : G[u]) {
        if(ne != fa) {
            if(ne == b) {
                if((sxor ^ w) == 0) {
                    ok = 1;
                    return ;
                }
                continue;
            }
            dfs1(ne, u, sxor ^ w);
        }
    }
}

void dfs2(int u, int fa, int sxor) {
    if(ok) return ;
    B[u] = sxor;
    for(auto [ne, w] : G[u]) {
        if(ne != fa) {
            dfs2(ne, u, sxor ^ w);
        }
    }
}

void solve() {
    cin >> n >> a >> b;
    ok = 0;
    for(int i = 1; i <= n; i ++ ) {
        G[i].clear();
        A[i] = B[i] = -1;
    }
    for(int i = 1; i < n; i ++ ) {
        int u, v, w;
        cin >> u >> v >> w;
        G[u].pb({v, w});
        G[v].pb({u, w});
    }
    dfs1(a, -1, 0);
    dfs2(b, -1, 0);
    if(ok) {
        yon(1);
        return ;
    }
    vector<int> aa, bb;
    for(int i = 1; i <= n; i ++ ) {
        if(A[i] != -1) {
            aa.pb(A[i]);
        }
        if(i != b) {
            bb.pb(B[i]);
        }
    }
    sort(all(aa));
    sort(all(bb));
    aa.erase(unique(all(aa)), aa.end());
    bb.erase(unique(all(bb)), bb.end());
    int ans = 0;
    int i = 0, j = 0;
    while(i < sz(aa) && j < sz(bb)) {
        if(aa[i] < bb[j]) i ++;
        else if(aa[i] > bb[j]) j ++;
        else {
            ans = 1;
            break;
        }
    }
    yon(ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值