AtCoder Beginner Contest 318

咕咕咕,总力战还没打,凹不过卷狗,躺了.jpg

A - Full Moon (abc318 A)

题目大意

给定\(n, m, p\),问有多少个 \(i\)满足 \(0 < m+pi \leq n\)

解题思路

减去初始的\(m\),剩下的就是看 \(p\)的倍数个数。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, p;
    cin >> n >> m >> p;
    cout << (n - m) / p + (n >= m) << '\n';

    return 0;
}



B - Overlapping sheets (abc318 B)

题目大意

一个二维空间,有\(n\)个矩形覆盖。

问有多大的空间被覆盖了。重复覆盖算一次。

解题思路

空间大小只有\(100\),矩形只有 \(100\),对每个矩形直接 \(O(100^2)\)更新格子的覆盖情况,最后统计被覆盖的个数即可。

时间复杂度为 \(O(100^3)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    array<array<int, 101>, 101> tu{};
    while (n--) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        for (int i = a; i < b; ++i)
            fill(tu[i].begin() + c, tu[i].begin() + d, 1);
    }
    int ans = 0;
    for (auto& i : tu)
        ans += count(i.begin(), i.end(), 1);
    cout << ans << '\n';

    return 0;
}



C - Blue Spring (abc318 C)

题目大意

\(n\)天旅行,第 \(i\)天旅行花费 \(f_i\)元。

你可以购买一日游券,可以免去任意一天的旅游费用。

但是一日游券是捆绑售卖的,你需要花 \(p\)元买 \(d\)张 一日游券。这意味着你可以免去任意\(d\)天的旅游费用。你可以购买多次,最后一日游券可以剩余。

问旅游完 \(n\)天所需要的最小费用。

解题思路

假设我购买了\(x\)张一日游券,我肯定是免去旅游费用最高的那 \(x\)天的费用。

那就直接枚举买多少次一日游券,用于免去最高的旅游费用,然后其费用与剩余天数的费用相加求个最小值即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, d, p;
    cin >> n >> d >> p;
    vector<int> f(n);
    for (auto& i : f)
        cin >> i;
    sort(f.begin(), f.end(), greater<int>());
    LL sum = accumulate(f.begin(), f.end(), 0ll);
    LL ans = sum;
    for (int i = 0; i < n; i += d) {
        for (int j = i; j < min(n, i + d); ++j)
            sum -= f[j];
        sum += p;
        ans = min(ans, sum);
    }
    cout << ans << '\n';

    return 0;
}



D - General Weighted Max Matching (abc318 D)

题目大意

给定一张无向图,边有边权。要求选择一些边,最大化边权和,使得每条边的顶点不重复。

解题思路

点数只有\(16\),考虑到每条边的顶点不能重复,那么当我们考虑某条边能不能选时,必须得知道目前已经选了哪些点,以决定这条边能不能选,因此设 \(dp[i]\)表示选择的点的情况是 \(i\)(二进制压缩的状态,0表示不选,1表示选) 的最大边权和。然后枚举一条边是否选择,转移到下一个状态即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<vector<int>> edge(n);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n - i - 1; ++j) {
            int x;
            cin >> x;
            edge[i].push_back(x);
        }
    }
    vector<LL> dp((1 << n), 0);
    for (int i = 0; i < (1 << n); ++i) {
        for (int j = 0; j < n; ++j) {
            if ((~i >> j) & 1)
                continue;
            for (int k = j + 1; k < n; ++k) {
                if ((~i >> k) & 1)
                    continue;
                int w = edge[j][k - j - 1];
                dp[i] = max(dp[i], dp[i ^ (1 << j) ^ (1 << k)] + w);
            }
        }
    }
    cout << *max_element(dp.begin(), dp.end()) << '\n';

    return 0;
}



E - Sandwiches (abc318 E)

题目大意

给定一个\(n\)个数的数组 \(a\),问有多少个 \((i, j, k)\),满足

  • \(i < j < k\)
  • \(a_i = a_k\)
  • \(a_i \neq a_k\)

解题思路

由于数有\(10^5\),我们只能枚举其中一个。要么枚举 \(i\)要么枚举 \(j\)。分别考虑后发现枚举 \(j\)简单一点。

如果枚举下标\(i\)会发现比较难计算,对于当前的 \(a_i\),那么对于之后的每个 \(a_k\)都要考虑它们之间的其他数的个数,感觉复杂度会比较大。

考虑枚举下标\(j\),剩下的就是考虑 \(j\)左边和 \(j\)右边的相同数。这个\(j\)对答案的贡献就是 \(\sum_{x \neq a_j} l_{x} \times r_{x}\),其中 \(l_x\)表示 \(j\)左边 \(x\)(是值,不是下标)的个数, \(r_x\)就是右边的 \(x\)的个数。

考虑当 \(j\)移动时,只有 \(a_j\)\(a_{j + 1}\)这两个数的个数发生变化。因此我们事先维护 \(\sum\)的值,\(\sum - l_{a_j} \times r_{a_j}\)就是此时的 \(j\)对答案的贡献。转移时通过加减那两个数的贡献,就得到 \(j\)移动到下一个数时的 \(\sum\)的值。然后累计就是答案了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<int> a(n);
    vector<int> l(n, 0), r(n, 0);
    for (auto& i : a) {
        cin >> i;
        --i;
        r[i]++;
    }
    LL ans = 0;
    LL tmp = 0;
    for (int i = 0; i < n; ++i) {
        tmp -= 1ll * l[a[i]] * r[a[i]];
        r[a[i]]--;
        ans += tmp;
        l[a[i]]++;
        tmp += 1ll * l[a[i]] * r[a[i]];
    }
    cout << ans << '\n';

    return 0;
}



F - Octopus (abc318 F)

题目大意

给定\(n\)个宝藏的位置,以及\(n\)只长度为 \(l_i\)的手。你要求合法的位置的个数。

一个位置\(x\)合法,意味着你可以用\(n\)只手拿到 \(n\)个宝藏。一只手只能拿一个宝藏,且拿的范围为 \([x - l_i, x + l_i]\)

解题思路

考虑合法位置的情况,分三种,最左边,最右边,以及两个宝藏之间。

然后考虑最左边的种类,很显然,我们对手的长度进行从小到大排序,那就根据宝藏的坐标从小到大分配手。即对于第\(i\)个宝藏,分配的是长度为 \(l_i\)的手,那么能够及该宝藏的最左边的距离就是 \(x_i - l_i\),对该距离取个最大值,就是最左边的合法位置了。

然后考虑这个合法位置不断往右边移动,在到达第一个宝藏的位置前都是合法位置。

然后合法位置到了第一个宝藏和第二个宝藏之间。考虑在这区间移动,我们的手分配宝藏的方案是不变的,直到第二个宝藏距离当前位置比第一个位置更近时,分配方案才发生变化,需要重新计算。

注意到需要重新计算的情况,是宝藏距离当前位置的排名发生了变化,因此手分配宝藏的方案需要改变,重新计算当前位置的合法性。而排名发生变化的位置被称为关键位置

考虑这些关键位置在哪,既然是排名发生变化,那就是在某两个宝藏的中点,因此枚举左右两边宝藏,求个中点就能得到这些关键位置,注意到关键位置的数量只有\(O(n^2)\)个。

对于每个关键位置,重新计算下各个宝藏到该位置的距离,然后排个序分配手,在区间[当前关键位置,下一个关键位置-1],其手的分配方案都不变,此时对于左边的宝藏,会有一个最小值 \(L = x_i + l_i\),而对于右边的宝藏,会有个最大值 \(R = x_i - l_i\),这之间的[R, L]就是合法位置的区间。

因此对所有这样的关键位置累计\(L - R + 1\)的值就是答案。总的时间复杂度是 \(O(n^3 \log n)\)

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(void) {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n;
    cin >> n;
    vector<LL> x(n), l(n);
    for (auto& i : x)
        cin >> i;
    for (auto& i : l)
        cin >> i;
    LL ans = 0;
    LL L = numeric_limits<LL>::min(), R = numeric_limits<LL>::max();
    for (int i = 0; i < n; ++i) {
        L = max(L, x[i] - l[i]);
        R = min(R, x[i] + l[n - i - 1]);
    }
    ans += max(0ll, x[0] - L);
    ans += max(0ll, R - x[n - 1] + 1);
    vector<int> id(n);
    vector<int> rk(n);
    iota(id.begin(), id.end(), 0);
    for (int i = 0; i < n - 1; ++i) {
        vector<LL> div{x[i]};
        for (int j = 0; j <= i; ++j) {
            for (int k = i + 1; k < n; ++k) {
                LL mid = (x[j] + x[k] + 1) / 2;
                if (mid > x[i] && mid < x[i + 1])
                    div.push_back(mid);
            }
        }
        sort(div.begin(), div.end());
        div.push_back(x[i + 1]);
        for (int k = 0; k < div.size() - 1; ++k) {
            LL mid = div[k];
            vector<LL> pos(n);
            for (int j = 0; j < n; ++j) {
                pos[j] = abs(x[j] - mid);
            }
            sort(id.begin(), id.end(), [&](int a, int b) {
                if (pos[a] != pos[b])
                    return pos[a] < pos[b];
                else
                    return a > b;
            });
            for (int j = 0; j < n; ++j)
                rk[id[j]] = j;
            L = div[k + 1] - 1, R = div[k];
            for (int j = 0; j <= i; ++j) {
                L = min(L, x[j] + l[rk[j]]);
            }
            for (int j = i + 1; j < n; ++j)
                R = max(R, x[j] - l[rk[j]]);
            ans += max(0ll, L - R + 1);
        }
    }
    cout << ans << '\n';

    return 0;
}




其实上面想复杂了(其实写也写复杂了,其实可以直接\(O(n^2)\),枚举宝藏求中点,然后遍历所有关键点,上述这么写是因为当时考虑的每两个宝藏之间的情况,所以就一段一段的),不过是一个很朴素的从特殊位置开始的思路。

还是考虑关键位置,这个关键位置可以是两个宝藏的中点,它们的距离排名发生了变化;也可以是某个宝藏使用某只手的极限位置(即\(x_i - l_j\)\(x_i + l_j\),以这些极限距离作为分割点\(div_i\),分别考虑每个分割点是否合法(同上面一样距离排序然后分配手),如果可以则代表 \([div_i, div_{i+1} - 1]\)都可以。可以参考jiangly的代码,但是正确性不太能理解,因为是以最优分配方式判断合法性,能保证这个区间都能取到最优方案吗(?

G - Typical Path Problem (abc318 G)

题目大意

给定一张简单无向图,问是否存在一个简单路径,从点\(a\)出发经过点 \(b\)到达点 \(c\)

解题思路

题目可以转化成是否存在一棵生成树,其根为\(b\),点 \(a\)和点 \(c\)\(lca\)是根,然后就一直在研究生成树,就不会了

神奇的代码



Ex - Count Strong Test Cases (abc318 Ex)

题目大意

<++>

解题思路

<++>

神奇的代码



热门相关:我有一座恐怖屋   女婿面试   MZ世代的老友哥哥   名门盛婚:首席,别来无恙!   名门盛婚:首席,别来无恙!