Codeforces Round 958 (Div. 2) A~E

A. Split the Multiset (思维)

题意:

有一个多集 S S S 。最初,多集仅包含一个正整数 n n n 。即 S = { n } S=\{n\} S={n} 。此外,还有一个给定的正整数 k k k

在一个操作中,您可以选择 S S S 中的任意正整数 u u u ,并从 S S S 中删除一个 u u u 的副本。然后,将不超过 k k k 个正整数插入 S S S ,以便所有插入的整数之和等于 u u u

找出使 S S S 包含 n n n 个 1 的最少操作数。

分析:

将题目转化为需要将 1 1 1个数字分成 n n n个部分,也就是要分出 n − 1 n - 1 n1个部分,每次可以分出 k − 1 k - 1 k1个部分。答案为 ( n − 1 ) / ( k − 1 ) (n-1)/(k-1) (n1)/(k1)向上取整。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        int n, k;
        cin >> n >> k;
        cout << ceil((n - 1) / (double)(k - 1)) << endl;
    }
    return 0;
}

B.Make Majority (思维)

题意:

给定一个序列 [ a 1 , … , a n ] [a_1,\ldots,a_n] [a1,,an] ,其中每个元素 a i a_i ai 0 0 0 1 1 1 。现在可以对该序列应用多个(可能为零个)操作。在每个操作中,选择两个整数 1 ≤ l ≤ r ≤ ∣ a ∣ 1\le l\le r\le |a| 1lra (其中 ∣ a ∣ |a| a a a a 的当前长度),并将 [ a l , … , a r ] [a_l,\ldots,a_r] [al,,ar] 替换为单个元素 x x x ,其中 x x x [ a l , … , a r ] [a_l,\ldots,a_r] [al,,ar] 的多数。

这里,由 0 0 0 1 1 1 组成的序列的多数定义如下:假设序列中分别有 c 0 c_0 c0 个零和 c 1 c_1 c1 个一。

  • 如果是 c 0 ≥ c 1 c_0\ge c_1 c0c1 ,则多数为 0 0 0
  • 如果是 c 0 < c 1 c_0 < c_1 c0<c1 ,则多数为 1 1 1

例如,假设 a = [ 1 , 0 , 0 , 0 , 1 , 1 ] a=[1,0,0,0,1,1] a=[1,0,0,0,1,1] 。如果我们选择 l = 1 , r = 2 l=1,r=2 l=1,r=2 ,则结果序列将为 [ 0 , 0 , 0 , 1 , 1 ] [0,0,0,1,1] [0,0,0,1,1] 。如果我们选择 l = 4 , r = 6 l=4,r=6 l=4,r=6 ,则结果序列将为 [ 1 , 0 , 0 , 1 ] [1,0,0,1] [1,0,0,1]

确定您是否可以通过有限数量的操作来制作 a = [ 1 ] a=[1] a=[1]

分析:

我们可以将 a a a中的所有连续的 0 0 0合并成一个 0 0 0,此时我们只需要对比 1 1 1 0 0 0的数量即可,当 c 1 > c 0 c_1 > c_0 c1>c0 就可以生成 1 {1} 1

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        string s;
        cin >> s;
        int c0 = 0, c1 = 0;
        string tmp(s);
        tmp += '1';
        for (int i = 0; i < n; i++)
        {
            if (tmp[i] == '1')
            {
                c1++;
            }
            else if (tmp[i] == '0' && tmp[i + 1] == '1')
            {
                c0++;
            }
        }
        if (c1 > c0)
        {
            cout << "Yes" << endl;
        }
        else
        {
            cout << "No" << endl;
        }
    }
    return 0;
}

C.Increasing Sequence with Fixed OR (思维)

题意:

给定一个正整数 n n n 。找出满足以下条件的最长正整数序列 a = [ a 1 , a 2 , … , a k ] a=[a_1,a_2,\ldots,a_k] a=[a1,a2,,ak] ,并输出该序列:

  • 对于所有 1 ≤ i ≤ k 1\le i\le k 1ik ,结果为 a i ≤ n a_i\le n ain
  • 对于所有 2 ≤ i ≤ k 2\le i\le k 2ik ,结果为 a a a ,结果为严格递增。也就是说,对于所有 2 ≤ i ≤ k 2\le i\le k 2ik ,结果为 a i > a i − 1 a_i > a_{i-1} ai>ai1
  • 对于所有 2 ≤ i ≤ k 2\le i\le k 2ik ,结果为 a i   ∣   a i − 1 = n a_i\,|\,a_{i-1}=n aiai1=n ,其中 ∣ | 表示

分析:

我们先将 n n n转化为二进制。观察样例后发现,如果 n n n的二进制含有 x x x 1 1 1,那么序列长度为 x + 1 x+1 x+1。然后考虑如何构造。我们只需要依次去掉低位的 1 1 1,就可以满足以上要求。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        LL n;
        cin >> n;
        vector<LL> ans;
        ans.push_back(n);
        for (LL i = 0; i < 63; i++)
        {
            if (n >> i & 1)
            {
                LL ins = n - ((LL)1 << i);
                if (ins == 0)
                    continue;
                ans.push_back(n - ((LL)1 << i));
            }
        }
        reverse(ans.begin(), ans.end());
        int k = ans.size();
        cout << k << endl;
        for (auto x : ans)
        {
            cout << x << " ";
        }
        cout << endl;
    }
    return 0;
}

D.The Omnipotent Monster Killer(dp)

题意:

在编号为 i i i ( 1 ≤ i ≤ n 1\le i\le n 1in ) 的顶点上,有一个攻击点数为 a i a_i ai 的怪物。你想与怪物战斗 1 0 100 10^{100} 10100 轮。
在每个回合中,以下情况按顺序发生:

  1. 所有活着的怪物都会攻击你。你的生命值会减少所有活着的怪物攻击点的总和。
  2. 你选择一些(可能是全部或没有)怪物并杀死它们。被杀死后,怪物将无法在将来进行任何攻击。
    有一个限制:在一轮中,你不能杀死两个由边直接连接的怪物。
    如果你选择最佳攻击的怪物,那么在所有回合之后你的最小生命值减少量是多少?

分析:

我们希望每次砍掉的点权值和尽可能大,可以发现总操作次数不会很多,即使是给树黑白染色,然后砍掉权值较大的部分也可以让总权值减少至少一半。所以最多只用 60 60 60次操作就可以删掉所有的点。
假设一个节点在第 i i i个回合删除,那么它的伤害是 i × a i i \times a_i i×ai f [ x ] [ j ] f[x][j] f[x][j]表示 x x x节点在 j j j回合删除,并且以 x x x为根的子树的伤害最小值,那么有 f [ x ] [ j ] = j × a x + ∑ m i n ( f [ i ] [ k ] ) , k ! = j f[x][j]=j \times a_x +\sum min(f[i][k]) ,k!=j f[x][j]=j×ax+min(f[i][k]),k!=j。最终答案为 m i n ( f [ 0 ] [ j ] ) min(f[0][j]) min(f[0][j])

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        LL n;
        cin >> n;
        vector<LL> a(n);
        vector<vector<LL>> g(n, vector<LL>());
        vector<vector<LL>> f(n, vector<LL>(21, 0));
        for (int i = 0; i < n; i++)
            cin >> a[i];
        for (int i = 0; i < n - 1; i++)
        {
            int a, b;
            cin >> a >> b;
            a--, b--;
            g[a].push_back(b), g[b].push_back(a);
        }
        auto dfs = [&](auto self, int u, int v) -> void
        {
            for (int i = 1; i <= 20; i++)
            {
                f[u][i] = i * a[u];
            }
            for (auto x : g[u])
            {
                if (x == v)
                    continue;
                self(self, x, u);
                for (int i = 1; i <= 20; i++)
                {
                    LL mi = 1e18;
                    for (int j = 1; j <= 20; j++)
                    {
                        if (i == j)
                            continue;
                        mi = min(mi, f[x][j]);
                    }
                    f[u][i] += mi;
                }
            }
        };
        dfs(dfs, 0, -1);
        LL ans = 1e18;
        for (int i = 1; i <= 20; i++)
        {
            ans = min(ans, f[0][i]);
        }
        cout << ans << endl;
    }
    return 0;
}

E.Range Minimum Sum (数据结构)

题意:

对于长度为 n n n 的数组 [ a 1 , a 2 , … , a n ] [a_1,a_2,\ldots,a_n] [a1,a2,,an] ,将 f ( a ) f(a) f(a) 定义为所有子段的最小元素之和。即

f ( a ) = ∑ l = 1 n ∑ r = l n min ⁡ l ≤ i ≤ r a i . f(a)=\sum_{l=1}^n\sum_{r=l}^n \min_{l\le i\le r}a_i. f(a)=l=1nr=lnlirminai.

排列是从 1 1 1 n n n 的整数序列,长度为 n n n ,每个数字只包含一次。给定一个排列 [ a 1 , a 2 , … , a n ] [a_1,a_2,\ldots,a_n] [a1,a2,,an] 。对于每个 i i i ,解决以下问题:

  • a a a 中删除 a i a_i ai ,连接剩余部分,得到 b = [ a 1 , a 2 , … , a i − 1 ,    a i + 1 , … , a n ] b = [a_1,a_2,\ldots,a_{i-1},\;a_{i+1},\ldots,a_{n}] b=[a1,a2,,ai1,ai+1,,an]
  • 计算 f ( b ) f(b) f(b)

分析:

我们先考虑如果不删掉一个元素的操作,可以用 s e t set set进行解决,这些东西可以处理出一个点 u u u左右侧第一个小的位置 l , r l,r l,r。那么所有左端点为 [ l + 1 , u ] [l+1,u] [l+1,u],右端点位于 [ u , r − 1 ] [u,r-1] [u,r1]的区间都满足最小值为 a u a_u au。我们先计算起始的答案,再考虑删掉一个数 u u u改变了什么。

  • 首先所有最小值为 a u a_u au的区间被删掉了,这部分我们计算起始答案的时候就算了,记录一下即可。
  • 对于 a i < a u a_i < a_u ai<au的位置,如果 ( i , u ) (i,u) (i,u)之间没有 < a i < a_i <ai的数字,那么最小值为 a i a_i ai的区间也会少掉一些贡献。这个贡献其实是个静态的区间加,可以差分解决。
  • 对于 a i > a u a_i > a_u ai>au的位置,因为 a u a_u au被删掉了, a i a_i ai为最小值的区间增加了,这类贡献我们可以在 a i a_i ai处统计。我们可以找到 a i a_i ai后面第一个 < a i < a_i <ai的位置 j j j,那么 a j a_j aj被删掉时的最小值为 a i a_i ai的区间就会增多,我们再找到 a i a_i ai后面第二个 < a i < a_i <ai的位置 k k k,增多的数量就是 ( k − j − 1 ) × ( i − l i ) (k-j-1) \times(i-l_i) (kj1)×(ili)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
#define endl '\n'
#define PII pair<LL, LL>
const int INF = 1e9;
const int mod = 1e9 + 7;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--)
    {
        int n;
        cin >> n;
        vector<int> a(n + 1), p(n + 1);
        for (int i = 1; i <= n; i++)
            cin >> a[i], p[a[i]] = i;
        set<int> s;
        s.insert(0);
        s.insert(n + 1);
        vector<LL> num(n + 1);
        LL sum = 0;
        vector<LL> d(n + 1);
        auto add = [&](int l, int r, LL v)
        {
            if (l > r)
                return;
            d[l] += v;
            if (r + 1 <= n)
                d[r + 1] -= v;
        };
        for (int i = 1; i <= n; i++)
        {
            int u = p[i];
            int L = *prev(s.lower_bound(u));
            int R = *s.upper_bound(u);
            num[u] = 1LL * i * (u - L) * (R - u);
            sum += num[u];
            add(L + 1, u - 1, -1LL * i * (R - u));
            add(u + 1, R - 1, -1LL * i * (u - L));
            {
                auto it = s.upper_bound(u);
                if (it != s.end())
                {
                    auto tmp = next(it);
                    if (tmp != s.end())
                    {
                        int p1 = *it, p2 = *tmp;
                        num[p1] -= 1LL * i * (p2 - p1 - 1) * (u - L);
                    }
                }
            }
            {
                auto it = s.lower_bound(u);
                if (it != s.begin())
                {
                    it = prev(it);
                    if (it != s.begin())
                    {
                        auto pre = prev(it);
                        int p1 = *it, p2 = *pre;
                        num[p1] -= 1LL * i * (p1 - p2 - 1) * (R - u);
                    }
                }
            }
            s.insert(u);
        }
        for (int i = 1; i <= n; i++)
        {
            d[i] += d[i - 1];
            cout << sum - num[i] + d[i] << " ";
        }
        cout << endl;
    }
    return 0;
}

赛后交流

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值