Codeforces Round 935 (Div. 3) A~F

A.Setting up Camp(思维)

题意:

组委会计划在游览结束后带领奥林匹克运动会的参赛选手进行徒步旅行。目前,正在计算需要的帐篷数量。据了解,每个帐篷最多可容纳 3 3 3人。

在参赛者中,有 a a a个内向者, b b b个外向者, c c c个普通人:

  • 每个内向者都想独自住在帐篷里。因此,内向者的帐篷里必须只有一个人–只有内向者自己。
  • 每个外向的人都想和另外两个人住在一个帐篷里。因此,一个外向者的帐篷里必须正好有三个人。
  • 每个人都可以选择任何一种方式(独居、与他人同住或与他人同住)。

组委会非常尊重每位参赛者的意愿,因此他们希望满足所有参赛者的愿望。

请告诉我们至少需要多少顶帐篷,以便所有参加者都能根据自己的喜好找到住处。如果无法满足所有参与者的愿望,输出 − 1 -1 1

分析:

内向者一人一个帐篷,普通人哪里不够塞哪里。外向者必须三人一个帐篷,优先给外向者三人分一个帐篷,若剩下不足三人,用普通人补上,如果补不上,就无解,否则有解。

代码:

#include<bits/stdc++.h>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int a, b, c;
        cin >> a >> b >> c;
        int d = (3 - b % 3) % 3;
        int ans = a + (b + c + 2) / 3;
        if (d > c)
            cout << -1 << endl;
        else
            cout << ans << endl;
    }
    return 0;
} 

B.Fireworks(贪心)

题意:

徒步旅行的其中一天恰逢节假日,因此决定晚上在营地安排一场节日焰火表演。为此,徒步旅行的组织者购买了两个烟花发射装置和大量的烟花弹。

两个装置同时开启。第一个装置每隔 a a a分钟(即发射后 a , 2 ⋅ a , 3 ⋅ a , … a,2\cdot a,3\cdot a,\dots a,2a,3a,分钟)发射一次烟花。第二个装置每隔 b b b分钟(即发射后 b , 2 ⋅ b , 3 ⋅ b , … b,2\cdot b,3\cdot b,\dots b,2b,3b,分钟)发射一次烟花。

每个烟花在发射后的 m + 1 m+1 m+1分钟内都可以在天空中看到,也就是说,如果一个烟花是在装置开启后的 x x x分钟后发射的,那么从 x x x x + m x+m x+m(包括首尾两分钟),每分钟都可以看到一个烟花。如果一个烟花在另一个烟花 m m m分钟后发射,则两个烟花的可见时间均为一分钟。

天空中最多可以同时看到多少枚烟花?

分析:

题意可以转换为找一个时间段,长为 m + 1 m+1 m+1,使得这个时间段上释放的烟花最多。贪心的想,这个时间段左端点应该是两个烟花同时释放,因此 a a a b b b的最小公倍数即是两个烟花同时释放的时刻。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        LL a, b, m;
        cin >> a >> b >> m;
        cout << (m + a) / a + (m + b) / b << endl;
    }
    return 0;
}

C.Left and Right Houses(前缀和)

题意:

莱托沃村有 n n n栋房子。村民们决定修建一条大路,将村庄分为左右两边。每个居民都想住在街道的右侧或左侧,这可以描述为一个序列 a 1 , a 2 , … , a n a_1,a_2,\dots,a_n a1,a2,,an,如果 j j j这栋房子的居民想住在街道的左侧,则为 a j = 1 a_j=1 aj=1;否则为 a j = 0 a_j=0 aj=0

道路将从两栋房子之间穿过。左边的房子将被宣布为左侧,右边的房子将被宣布为右侧。更具体地说,让道路从房屋 i i i i + 1 i+1 i+1之间通过。那么位于 1 1 1 i i i之间的房屋将位于街道的左侧,位于 i + 1 i+1 i+1 n n n之间的房屋将位于街道的右侧。在这种情况下,整个村庄将被分割为右侧或左侧。

为了使设计公平,我们决定在铺设道路时,至少要让村子两边各一半的居民对选择感到满意。也就是说,在一边的 x x x居民中,至少有 ⌈ x 2 ⌉ \lceil\frac{x}{2}\rceil 2x希望住在那一边,其中 ⌈ x ⌉ \lceil x\rceil x表示四舍五入的实数 x x x

在路的左边,会有 i i i幢房子,在相应的 a j a_j aj中,至少要有 ⌈ i 2 ⌉ \lceil\frac{i}{2}\rceil 2i 0 0 0。路的右边有 n − i n-i ni座房子,在相应的 a j a_j aj中至少要有 ⌈ n − i 2 ⌉ \lceil\frac{n-i}{2}\rceil 2ni 1 1 1

确定在哪座房子 i i i之后铺设道路,以满足所述条件,并尽可能靠近村庄中央。形式上,在所有合适的位置 i i i中,最小化 ∣ n 2 − i ∣ \left|\frac{n}{2}-i\right| 2ni

如果有多个合适的位置 i i i,最小值 ∣ n 2 − i ∣ \left|\frac{n}{2}-i\right| 2ni ,则输出较小的一个。

分析:

分割一个序列,使其左半部分的 0 0 0要多于 1 1 1,右半部分的 1 1 1要多于 0 0 0
前缀和统计 0 0 0 1 1 1的数量,然后枚举分割线的位置即可。

代码:

#include<bits/stdc++.h>

using namespace std;

void solve() {
    int n;
    cin >> n;
    string s;
    cin >> s;
    s = " " + s;
    vector<int> num0(n + 1), num1(n + 1);
    for (int i = 1; i <= n; i++) {
        if (s[i] == '0')
            num0[i]++;
        else
            num1[i]++;
        num0[i] += num0[i - 1];
        num1[i] += num1[i - 1];
    }
    int pos = -1;
    if (num1[n] >= (n + 1) / 2) pos = 0;
    for (int i = 1; i <= n; i++) {
        int suml = num0[i];
        int sumr = num1[n] - num1[i];
        if (suml >= (i + 1) / 2 && sumr >= (n - i + 1) / 2) {
            if (pos == -1)
                pos = i;
            else {
                double dis0 = abs(double(n) / 2 - i);
                double dis1 = abs(double(n) / 2 - pos);
                if (dis0 < dis1)
                    pos = i;
            }
        }
    }
    cout << pos << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

D.Seraphim the Owl(思维)

题意:

大家排成长度为 n n n的一队,从 i = 1 i=1 i=1号开始,向猫头鹰谢拉菲姆请教生命的意义。不幸的是,基里尔当时正忙着为这个问题编写传说,所以他来得晚了一些,站在了这 n n n个人之后,排在了队伍的最后。基里尔对这种情况完全不满意,于是他决定贿赂一些排在他前面的人。

对于队列中的第 i i i个人,基里尔知道两个值: a i a_i ai b i b_i bi。如果此刻基里尔站在位置 i i i上,那么他可以选择任意一个位置 j j j,并与位置 j j j的人交换位置。在这种情况下,基里尔需要支付他 a j a_j aj个金币。而对于 j < k < i j\lt k\lt i j<k<i的每一个 k k k,基里尔都要向位置 k k k的人支付 b k b_k bk个金币。基里尔可以多次执行这个操作。

基里尔很节俭,所以他想花尽可能少的硬币,但是他又不想等太久,所以基里尔认为他应该排在 m m m人的前面。

请帮助基里尔确定,为了不等太久,他最少需要花费多少金币。

分析:

i > m i>m i>m时 如果想跟第 i i i个人换位是不影响第 i + 1 i+1 i+1个人的决策的,也就是说可以先和第 i + 1 i+1 i+1个人换位再换到 i i i,也可以直接换到 i i i。以此类推大于 m m m时直接取 a i a_i ai b i b_i bi的最小值即可。

枚举停在前面所有位置上时的花费,不过发现只有停到的某个位置 i i i上必须支付 a i a_i ai以外,后面 i + 1 ∼ n i+1∼n i+1n的位置的代价都是 a j , b j a_j,b_j aj,bj值取其小值。所以我们从后向前枚举 i i i,后面部分的代价是不会变的,直接累加起来即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 2e5 + 5;
const LL inf = 0x3f3f3f3f3f3f3f;

int n, m;
LL a[N], b[N];

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++)
        cin >> b[i];
    LL sum = 0;
    LL mx = inf;
    for (int i = m + 1; i <= n; i++)
        sum += min(a[i], b[i]);
    for (int i = n - 1; i; i--)
        b[i] += b[i + 1];
    b[n + 1] = 0;
    for (int i = 1; i <= m; i++) {
        mx = min(a[i] + b[i + 1] - b[m + 1] + sum, mx);
    }
    cout << mx << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

E.Binary Search(二分)

题意:

安东在徒步旅行中感到无聊,想解决一些问题。他问基里尔有没有新问题,基里尔当然有。

给你一个大小为 n n n的排列数 p p p和一个需要找到的数字 x x x。长度为 n n n的排列是由 n n n个不同的整数组成的数组,这些整数从 1 1 1 n n n按任意顺序排列。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是一个排列( 2 2 2在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4]也不是一个排列( n = 3 n=3 n=3,但数组中有 4 4 4)。

你认为自己是一个很酷的程序员,所以你将使用一种高级算法进行搜索–二分查找。但是,你忘了二分查找必须对数组进行排序。

为了得到正确的答案,你可以在运行算法前执行不超过 2 2 2次的以下操作:选择索引 i i i, j j j( 1 ≤ i , j ≤ n 1\le i,j\le n 1i,jn),并交换位置 i i i j j j的元素。

然后进行二分查找。在算法开始时,声明了两个变量 l = 1 l=1 l=1 r = n + 1 r=n+1 r=n+1。然后执行以下循环:

  1. 如果 r − l = 1 r-l=1 rl=1,结束循环
  2. m = ⌊ r + l 2 ⌋ m=\lfloor\frac{r+l}{2}\rfloor m=2r+l
  3. p m ≤ x p_m\le x pmx,赋值 l = m l=m l=m,否则 r = m r=m r=m

我们的目标是在算法之前重新排列排列组合中的数字,以便在算法执行之后, p l p_l pl等于 x x x。可以证明, 2 2 2次运算总是足够的。

分析:

手推一下,整个区间的大部分数都是没用的,可以发现整个过程中 m m m一定不会等于 1 1 1,因为只有当 L = 1 L=1 L=1 R = 2 R=2 R=2 ( L + R ) / 2 (L+R)/2 (L+R)/2才会等于 1 1 1,但是 R − L = 1 R-L=1 RL=1时会终止,所以把 x x x换到位置 1 1 1能保证 x x x不出现在二分的过程中。然后模拟一遍二分的过程找出最终的 L L L,交换 1 1 1 L L L的位置即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 2e5 + 5;

int n, m;
int a[N], pos[N];

void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        pos[a[i]] = i;
    }
    cout << 2 << endl;
    int l = 1;
    int r = n + 1;
    int mid;
    cout << 1 << ' ' << pos[m] << endl;
    swap(a[1], a[pos[m]]);
    while (r - l != 1) {
        mid = l + r >> 1;
        if (a[mid] <= m)
            l = mid;
        else
            r = mid;
    }
    cout << l << ' ' << 1 << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

F.Kirill and Mushrooms(思维)

题意:

营地里的人一睡着,基里尔就偷偷溜出帐篷,去智慧橡树那里采蘑菇。

众所周知,橡树下生长着 n n n种蘑菇,每种蘑菇都有神奇的力量 v i v_i vi。基里尔非常想用这些蘑菇制作一种魔力最大的灵药。

灵药的强度等于其中蘑菇的数量与这些蘑菇中最小魔力的乘积。要配制灵药,基里尔要依次采摘生长在橡树下的蘑菇。基里尔可以按照任何顺序采集蘑菇。

然而,事情并非如此简单。智慧橡树告诉基里尔从 1 1 1 n n n的数字 p p p的排列组合。如果基里尔只采摘 k k k个蘑菇,那么所有指数为 p 1 , p 2 , … , p k − 1 p_1,p_2,\dots,p_{k-1} p1,p2,,pk1的蘑菇的魔力就会变成 0 0 0。基里尔不会使用魔力为零的蘑菇来配制灵药。

你的任务是帮助基里尔采集蘑菇,使他能够酿造出最大魔力的灵药。不过,基里尔有点害怕在橡树附近逗留太久,所以在所有合适的采集蘑菇的选项中,他要求你找到蘑菇数量最少的那个。

长度为 n n n的排列是由 n n n个不同的整数组成的数组,这些整数的顺序从 1 1 1 n n n。例如, [ 2 , 3 , 1 , 5 , 4 ] [2,3,1,5,4] [2,3,1,5,4]是一个排列,但 [ 1 , 2 , 2 ] [1,2,2] [1,2,2]不是排列( 2 2 2在数组中出现了两次), [ 1 , 3 , 4 ] [1,3,4] [1,3,4]也不是排列( n = 3 n=3 n=3,但 4 4 4在数组中出现)。

分析:

如果枚举出 k k k就可以确定那些蘑菇的力量值为 0 0 0,然后在剩下的蘑菇中贪心的选择最大的 k k k个,这样就可以知道选择的蘑菇中的最小魔力值。
multiset<int>维护两个集合,一个是 c n t cnt cnt表示当前不为 0 0 0且没有被选到的蘑菇, q q q是当前被选到的最大的 k k k个蘑菇。

然后从小到大枚举 k k k,每次都从 c n t cnt cnt中取最大值填入到 q q q中,直到 q . s i z e ( ) = = k q.size()==k q.size()==k。然后随着选择数量增大,一些蘑菇魔力变为 0 0 0,从两个集合中删掉一些蘑菇,每次删除时优先从 c n t cnt cnt中删除即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL inf = 1e18;

void solve() {
    LL n;
    cin >> n;
    multiset<LL> cnt, q;
    vector<LL> a(n + 5);
    vector<LL> p(n + 5);
    for (LL i = 1; i <= n; i++) {
        cin >> a[i];
        q.insert(a[i]);
    }
    for (LL i = 1; i <= n; i++)
        cin >> p[i];
    LL res = -inf;
    LL val = -inf;
    for (LL i = 1; i <= n; i++) {
        while (not q.empty() && cnt.size() < i) {
            LL x = *q.rbegin();
            cnt.insert(x);
            q.erase(q.find(x));
        }
        if (cnt.size() < i)
            break;
        LL v = *cnt.begin() * i;
        if (v > res) {
            res = v;
            val = i;
        }
        if (q.count(a[p[i]]))
            q.erase(q.find(a[p[i]]));
        else
            cnt.erase(cnt.find(a[p[i]]));
    }
    cout << res << " " << val << endl;
}

int main() {
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值