Bootstrap

Codeforces Round 975 (Div. 2)(A,B,C,D线段树解法,E)

比赛链接

https://codeforces.com/contest/2019

A题

思路

分别求出奇数位和偶数位的最大值,之后进行比较即可。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e2 + 5;
int n;
int a[N];
void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    int maxx1 = 0, maxx2 = 0, ans1 = 0, ans2 = 0;
    for (int i = 1, j = 2; i <= n; i += 2, j += 2)
    {
        maxx1 = max(maxx1, a[i]);
        ans1++;
        if (j <= n)
        {
            ans2++;
            maxx2 = max(maxx2, a[j]);
        }
    }
    cout << max(maxx1 + ans1, maxx2 + ans2) << endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}

B题

思路

我们考虑预处理出每一个区间对答案产生的贡献。考虑每个区间以第 a i a_{i} ai个为端点,到 a i + 1 a_{i+1} ai+1中间的数对答案产生的贡献。

对第 i i i个端点的贡献和为 ( n − i + 1 ) × ( i − 1 ) + ( n − i ) (n-i+1)\times (i-1) + (n-i) (ni+1)×(i1)+(ni),即以 a i a_{i} ai为端点产生的贡献。

对第 a i + 1 a_{i}+1 ai+1 a i + 1 a_{i+1} ai+1之间的数对答案产生的贡献为 i × ( n − i ) i\times (n-i) i×(ni)

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, q, k;
int a[N];
void solve()
{
    cin >> n >> q;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    map<int, int> mp;
    mp[n - 1] += (a[2] - a[1]);
    mp[n - 1]++;
    for (int i = 2; i < n; i++)
    {
        int op = n - i + 1;
        int cnt = i - 1;
        mp[cnt * op + (n - i)]++;
        mp[(op - 1) * (cnt + 1)] += (a[i + 1] - a[i] - 1);
    }
    while (q--)
    {
        cin >> k;
        cout << mp[k] << " ";
    }
    cout << endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}

C题

思路

可以分两种情况进行讨论。

根据题意,我们最多可以将卡牌分为 n n n组。

我们规定 s u m sum sum表示原先卡牌个数的总和, m a x x maxx maxx表示卡牌个数最多的那个卡牌类型的数量。

k = 0 k=0 k=0的时候,只需要对原先的卡牌进行分组即可。我们假设可以将每 i i i个卡牌分为一组,如果 s u m % i = = 0 sum \% i == 0 sum%i==0并且 s u m / i ≥ m a x x sum/i \ge maxx sum/imaxx,则可以将卡牌分为每 i i i个一组。我们直接对答案进行暴力枚举即可。

k > 0 k > 0 k>0的时候,我们依旧假设可以将每 i i i个卡牌分为一组,对于我们最后一组的卡牌,会有 s u m % i sum \% i sum%i个,如果我们想要将其补全,需要再添加 o p = i − s u m % i op = i - sum \% i op=isum%i个卡牌。如果 k < o p k < op k<op,则不可将卡牌分为每 i i i个一组。否则,我们令 r e s = ( s u m + o p ) / i + ( k − o p ) / i res = (sum + op) / i + (k - op) / i res=(sum+op)/i+(kop)/i,如果 r e s ≥ m a x x res \ge maxx resmaxx,则 i i i为可行的答案。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
int n, k, sum;
int a[N];
void solve()
{
    cin >> n >> k;
    sum = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];
    }
    int maxx = *max_element(a + 1, a + 1 + n);
    if (k == 0)
    {
        int ans = 1;
        for (int i = 1; i <= n; i++)
        {
            if (sum % i == 0 && sum / i >= maxx)
            {
                ans = i;
            }
        }
        cout << ans << endl;
    }
    else
    {
        int ans = 1;
        for (int i = 2; i <= n; i++)
        {
            int op = i - sum % i;
            if (op == i)
                op = 0;
            if (op > k)
                continue;
            int res = (sum + op) / i + (k - op) / i;
            if (res < maxx)
                continue;
            ans = i;
        }
        cout << ans << endl;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}

D题

思路

这里提供一个线段树的做法。

我们假设以点 i i i为起点,向两端不断扩散。对于 i i i点左边的点 j j j,其必须在 j j j点左端的点前被征服。对于 i i i点右边的点 k k k,其必须在 k k k点右端的点前被征服。

因此,我们可以预处理出前缀最小值和后缀最小值。

对于每一个起点 i i i,必然有一套单独的 v e c t o r < i n t > d f n vector<int>dfn vector<int>dfn表示每一个点最晚可以在什么时刻被征服,而这个 d f n dfn dfn值便是前面说的 i i i点左边点的前缀最小值,和 i i i点右边点的后缀最小值。

我们对于每一个 d f n i dfn_{i} dfni,将区间 [ d f n i , n ] [dfn_{i},n] [dfni,n]全部加上一个正整数 1 1 1。表示在 [ d f n i , n ] [dfn_{i},n] [dfni,n]这个时间节段的每一个节点已经被征服了多少个点。

处理完 d f n dfn dfn之后,我们对 1 1 1 n n n的区间,每一个点都减去对应的时间。即第 i i i个点减去 i i i

如果最后出现大于 0 0 0的情况,则表示第 i i i个点无法作为起点。

因此我们可以用线段树动态维护区间最大值,区间查询操作为加减,枚举每一个 i i i作为起点时是否可行,直接统计答案即可。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n;
int a[N], nxt[N];
vector<int>p(N);
struct segmenttree
{
    struct node
    {
        int l, r, maxx, tag;
    };
    vector<node>tree;

    segmenttree(): tree(1) {}
    segmenttree(int n): tree(n * 4 + 1) {}

    void pushup(int u)
    {
        auto &root = tree[u], &left = tree[u << 1], &right = tree[u << 1 | 1];
        root.maxx = max(left.maxx, right.maxx);
    }

    void pushdown(int u)
    {
        auto &root = tree[u], &left = tree[u << 1], &right = tree[u << 1 | 1];
        if (root.tag != 0)
        {
            left.tag += root.tag;
            right.tag += root.tag;
            left.maxx = left.maxx + root.tag;
            right.maxx = right.maxx + root.tag;
            root.tag = 0;
        }
    }

    void build(int u, int l, int r)
    {
        auto &root = tree[u];
        root = {l, r};
        if (l == r)
        {
            root.maxx = -p[r];
        }
        else
        {
            int mid = l + r >> 1;
            build(u << 1, l, mid);
            build(u << 1 | 1, mid + 1, r);
            pushup(u);
        }
    }

    void modify(int u, int l, int r, int val)
    {
        auto &root = tree[u];
        if (root.l >= l && root.r <= r)
        {
            root.maxx += val;
            root.tag += val;
            return;
        }
        pushdown(u);
        int mid = root.l + root.r >> 1;
        if (l <= mid) modify(u << 1, l, r, val);
        if (r > mid) modify(u << 1 | 1, l, r, val);
        pushup(u);
    }

    int query(int u, int l, int r)
    {
        auto &root = tree[u];
        if (root.l >= l && root.r <= r)
        {
            return root.maxx;
        }
        pushdown(u);
        int mid = root.l + root.r >> 1;
        int res = -inf;
        if (l <= mid) res = query(u << 1, l, r);
        if (r > mid) res = max(res, query(u << 1 | 1, l, r));
        return res;
    }
};
void init()
{
    iota(p.begin(), p.end(), 0);
}
void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    segmenttree smt(n);
    smt.build(1, 1, n);

    nxt[n] = a[n];
    for (int i = n - 1; i >= 1; i--)
    {
        nxt[i] = min(nxt[i + 1], a[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        smt.modify(1, nxt[i], n, 1);
    }
    int ans = 0, last = inf;
    for (int i = 1; i <= n; i++)
    {
        smt.modify(1, nxt[i], n, -1);
        smt.modify(1, 1, n, 1);
        if (smt.query(1, 1, n) <= 0) ans++;
        smt.modify(1, 1, n, -1);
        last = min(last, a[i]);
        smt.modify(1, last, n, 1);
    }
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    init();
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}

E题

思路

统计每一个节点所能到达的最大深度。

枚举以第 i i i层为结果计算答案,则第 i i i层的答案为深度 > i >i >i的节点的个数 + + +最大深度 < i <i <i的节点的个数。

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 5e5 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n;
int depth[N], maxdeep[N], cnt[N], pre[N];
vector<int> mp[N];
void dfs(int u, int fu, int deep)
{
    depth[u] = deep;
    maxdeep[u] = deep;
    cnt[deep]++;
    for (int j : mp[u])
    {
        if (j == fu) continue;
        dfs(j, u, deep + 1);
        maxdeep[u] = max(maxdeep[u], maxdeep[j]);
    }
}
void solve()
{
    cin >> n;
    for (int i = 0; i <= n; i++)
    {
        depth[i] = maxdeep[i] = cnt[i] = pre[i] = 0;
        mp[i].clear();
    }
    for (int i = 1, u, v; i < n; i++)
    {
        cin >> u >> v;
        mp[u].push_back(v);
        mp[v].push_back(u);
    }
    dfs(1, -1, 1);
    for (int i = 1; i <= n; i++)
    {
        pre[maxdeep[i]]++;
        cnt[i] += cnt[i - 1];
    }
    int ans = inf;
    for (int i = 1; i <= n; i++)
    {
        pre[i] += pre[i - 1];
        ans = min(ans, pre[i - 1] + n - cnt[i]);
    }
    cout << ans << endl;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int test = 1;
    cin >> test;
    for (int i = 1; i <= test; i++)
    {
        solve();
    }
    return 0;
}
;