跳转至

ABC353(A-G)

我的评价是:A-E 非常简单,\(F\)\(G\)

A - Buildings

题目大意

给你 \(N\) 个数字 \(H = (H_1, H_2, \cdots, H_N)\),找到第一个比 \(H_1\) 大的位置,如果不存在这样的数,输出 -1

参考代码
#include <iostream>

using namespace std;

int main()
{
    int n, h1, hi;
    cin >> n >> h1;
    for(int i = 2; i <= n; i++)
    {
        cin >> hi;
        if(hi > h1)
        {
            cout << i << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

B - AtCoder Amusement Park

题目大意

有一个娱乐设施最多能容纳 \(K\) 个人同时游玩。有 \(N\) 个旅行团正在排队,第 \(i\) 个旅行团有 \(A_i(A_i \le K)\) 个人。一个旅行团的人要么一起进去游玩,要么都不进去游玩。\(N\) 个旅行团正在排队,轮到一个旅行团的时候,如果设施内还有足够的空位,则这个旅行团一起进去游玩,否则他们会等到下一轮。问:最少需要多少轮才能让所有旅行团都游玩?

参考代码
#include <iostream>

using namespace std;

int main()
{
    int n, h1, hi;
    cin >> n >> h1;
    for(int i = 2; i <= n; i++)
    {
        cin >> hi;
        if(hi > h1)
        {
            cout << i << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

C - Sigma Problem

题目大意

对于正整数 \(x\)\(y\),定义 \(f(x, y)\)\(x+y\) 除以 \(10^8\) 的余数。

给你 \(N(2 \le N \le 3 \times 10^5)\) 个正整数,第 \(i\) 个数是 \(A_i(1 \le A_i < 10^8)\),求出 \(\sum\limits_{i=1}^{N-1}\sum\limits_{j=i+1}^{N} f(A_i, A_j)\)

解题思路

将数字排序,然后枚举 \(A_j\),在枚举时因为数组已经有序,且所有数字都小于 \(10^8\),可以二分出最后一个相加后不会溢出的位置 \(mid\),则 \([1, mid]\) 以及 \([mid+1, j-1]\) 都可以通过前缀和来快速计算出答案。

参考代码
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;
const int mod = 1E8;

int main()
{
    int n;
    cin >> n;
    vector<int> a(n+1);
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    sort(a.begin(), a.end());
    vector<LL> s(n+1);
    for(int i = 1; i <= n; i++)
        s[i] = s[i-1] + a[i];

    auto get_sum = [&](int l, int r) -> LL {return s[r] - s[l-1];};

    LL ans = 0;
    for(int i = 2; i <= n; i++)
    {
        int mid = lower_bound(a.begin()+1, a.begin() + i, mod-a[i]) - a.begin();
        if(mid)
            ans += get_sum(1, mid-1) + (LL)(mid-1) * a[i];
        if(mid < i)
            ans += get_sum(mid, i-1) + (LL)(i-mid) * (a[i] - mod);
    }
    cout << ans << endl;
    return 0;
}

D - Another Sigma Problem

题目大意

对于正整数 \(x\)\(y\),定义 \(f(x, y)\)

  • \(X\) 表示正整数 \(x\) 对应的字符串,\(Y\) 表示正整数 \(y\) 对应的字符串,则 \(X\)\(Y\) 字符串拼接的结果对应的正整数就是 \(f(x, y)\) 的结果。

例如,\(f(3, 14) = 314\)\(f(100, 1) = 1001\)

给你 \(N(2 \le N \le 2 \times 10^5)\) 个正整数,第 \(i\) 个数是 \(A_i(1 \le A_i < 10^9)\),求出 \(\sum\limits_{i=1}^{N-1}\sum\limits_{j=i+1}^{N} f(A_i, A_j)\),答案对 \(998244353\) 取模。

解题思路

显然 \(f(x, y)\) 是没有交换律的,所以不能排序。

\(|Y|\) 表示数字 \(y\) 对应的字符串的长度,则有 $f(x, y) = 10 ^ {|Y|} \times x + y $,我们可以枚举 \(j\),则对于 \(A_j = y\),贡献就是 \(\sum\limits_{i=1}^{j-1} f(A_i, A_j) = \sum\limits_{i=1}^{j-1}(10 ^ {|A_j|} \times A_i + A_j) = 10 ^ {|A_j|} \sum\limits_{i=1}^{j-1}A_i + (j-1) \times A_j\)

维护前缀和,上式可在 \(O(1)\) 时间内计算。

参考代码
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;
const int mod = 998244353;

int main()
{
    int n;
    cin >> n;
    vector<int> a(n+1);
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    vector<LL> s(n+1);
    for(int i = 1; i <= n; i++)
        s[i] = (s[i-1] + a[i]) % mod;

    auto get_sum = [&](int l, int r) -> LL {return (s[r] - s[l-1] + mod) % mod;};

    auto cal_len = [](int x) -> int {
        int len = 0;
        while(x)
        {
            len++;
            x /= 10;
        }
        return len;
    };

    vector<int> p{1};
    for(int i = 1; i <= 10; i++)
        p.emplace_back(p[i-1] * 10 % mod);
    LL ans = 0;
    for(int j = 2; j <= n; j++)
    {
        int k = cal_len(a[j]);
        ans += get_sum(1, j-1) * p[k] % mod + (LL)(j-1) * a[j] % mod;
        ans %= mod;
    }
    cout << ans << endl;
    return 0;
}

E - Yet Another Sigma Problem

题目大意

对于字符串 \(x\)\(y\),定义 \(f(x, y)\)\(x\)\(y\) 的最长公共前缀的长度。

给你 \(N(2 \le N \le 2 \times 10^5)\) 个字符串,第 \(i\) 个字符串是 \(S_i(\sum\limits_{i=1}^N|S_i| \le 3 \times 10^5)\),求出 \(\sum\limits_{i=1}^{N-1}\sum\limits_{j=i+1}^{N} f(S_i, S_j)\)

解题思路

考虑如何计算 \(f(S_i, S_j)\),暴力的算法无非就是一个下标开始枚举,找到第一个不相等的位置。我们会发现,在这个过程中,每有一个字符相等,那么答案就会 \(+1\)

因此,我们按以此将所有字符串插入一棵 Trie 树,插入字符串会让这个字符串对应的路径上的所有节点的权值都 \(+1\),则查询相当于查询字符串对应路径的权值之和。

实际写法更简单,只需要管插入,在插入时顺便把答案也查询出来就好了。

参考代码
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;
typedef long long LL;

const int maxn = 3E5 + 10;

class Trie
{
public:
    auto insert(const string &s) -> int 
    {
        int ans = 0, p = 1;
        for(auto ch : s)
        {
            int id = ch - 'a';
            if(!next[p][id])
                next[p][id] = idx++;
            p = next[p][id];
            ans += cnt[p]++;
        }
        return ans;
    }
private:
    int next[maxn][26], cnt[maxn], idx = 2;
}t;

int main()
{
    int n;
    cin >> n;
    LL ans = 0;
    string s;
    while(n--)
    {
        cin >> s;
        ans += t.insert(s);
    }
    cout << ans << endl;
    return 0;
}

F - Tile Distance

题目大意

在二维平面上,有两种砖块,一种是 \(1 \times 1\) 的小砖块,另一种是 \(K \times K\) 的大砖块。对于某个左下角坐标 \((i, j)\),如果 \(\lfloor\frac{i}{K}\rfloor + \lfloor\frac{j}{K}\rfloor\) 是偶数,则以 \((i, j)\) 作为左下角的 \(1 \times 1\) 范围是小砖块,否则以 \((i, j)\) 作为左下角的 \(K \times K\)​ 范围是大砖块。

砖块与砖块之前间互不重叠。

例如下图是 \(K=3\) 的情况下的例子:

img

在这个平面上,你可以进行移动,每一次移动从上下左右选择一个方向,移动 \(1\) 单位的距离。在大砖块内部移动无需花费金钱。当你从一个小砖块移动到相邻的大砖块,或者从大砖块移动到相邻的小砖块时,需要花费 \(1\) 元。

现在,你的起点是 \((S_x+0.5, S_Y+0.5)\),终点是 \((T_x+0.5, T_Y+0.5)\),问:最少花费多少元才能到达?

数据范围:

  • 所有的输入都是整数。
  • \(0 \le S_x, S_Y, T_x, T_Y \le 2 \times 10^{16}\)
解题思路

可以发现 \(k=1\) 时曼哈顿距离就是答案。

\(k > 3\) 时,可以发现斜着走到一个大格子只需要花费 \(2\),如下图所示。而使用这种方法,可以用 \(4\) 的花费走到一个直线相邻的格子,因此比较好的走法就是优先走斜的大格子,然后再走直线的小格子。

img

但是有一个特例是 \(k = 2\) 时,此时走直线也是 \(2\) 花费。

所以关键就是枚举起点和终点的大格子,按照大格子的计算方式即可。

代码赛时没有写出来。


G - Merchant Takahashi

题目大意

\(N(1 \le N \le 2 \times 10^5)\) 个 城镇,从城镇 \(i\) 移动到城镇 \(j\) 需要花费 \(C \times |i-j|\) 元,你是一个商人,初始的时候在城镇 \(1\)。接下里按照时间顺序有 \(M(1 \le M \le 2 \times 10^5)\) 个集会,第 \(i\) 个集会在城镇 \(T_i\) 举行,你可以决定参加这场集会,你将支付在城镇间移动的费用,同时收获 \(P_i\) 元。这些集会是按照时间顺序进行的,且任意两场集会的时间都不重合,城镇间移动所耗费的时间忽略不计。

初始时你有 \(0\) 元,在任意时刻,你的现有资金可以为负,问:在 \(M\) 次集会结束之后,你最多能拥有多少钱?

解题思路

每次集会只有去或者不去两种决策,如果按照线性 \(dp\) 的方式考虑,无法确定上一次的位置,因此 \(dp\) 的状态需要考虑商人的位置。

定义 \(dp[i][j]\) 表示前 \(i\) 场集会后商人的位置在城镇 \(j\) 的最佳答案,显然如果不参加第 \(i\) 场集会, \(dp[i][j]\) 的状态直接继承 \(dp[i-1][j]\),而如果参加,也仅仅会影响到 \(dp[i][T_i]\) 的值,其他的值仍然继承。因此可优化成一维的 \(dp[j]\)

对于第 \(i\) 场集会,令 \(j = T_i\) ,如果参加,则需要枚举上一次的位置 \(k\),转移式为: $$ dp[j] = \max_{k=1}^N(dp[k] + C \times |j-k| + P_i) $$ 拆开绝对值可得: $$ \begin{aligned} dp[j] & = \max(\max_{k=1}^j(dp[k] + C \times (j-k) + P_i), \max_{k=j}^N(dp[k] + C \times (k-j) + P_i)) \ & = \max{(\max_{k=1}^j(dp[k] - C \times k) + C \times j), (\max_{k=j}^N(dp[k] + C \times k) - C \times j)} + P_i \end{aligned} $$ 关键就是如何算出 \(\max\limits_{k=1}^j(dp[k] - C \times k)\)\(\max\limits_{k=j}^N(dp[k] + C \times k)\),只需要把 \(dp[k] - C \times k\)\(dp[k] + C \times k\) 都存到线段树里即可(所有节点都存两种值)。

计算出 \(dp[j]\) 之后,计算出新的 \(dp[j]-C \times j\)\(dp[j] + C \times j\),单点修改即可。

参考代码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef long long LL;
const LL pinf = -1E15;
class SegmentTree
{
public:
    SegmentTree(int n, LL C) : C(C), add(4*n), sub(4*n) 
    {
        build(1, 1, n);
        update(1, 1, n, 1, 0);
    }
    void build(int p, int beg, int end)
    {
        if(beg == end)
        {
            add[p] = sub[p] = pinf;
            return;
        }
        int mid = (beg + end) / 2;
        int lch = p * 2, rch = p * 2 + 1;
        build(lch, beg, mid);
        build(rch, mid+1, end);
        push_up(p);
    }
    LL get_add(int p, int beg, int end, int l, int r)
    {
        if(end < l || beg > r)
            return pinf;
        if(beg >= l && end <= r)
            return add[p];
        int mid = (beg + end) / 2;
        int lch = p * 2, rch = p * 2 + 1;
        return max(get_add(lch, beg, mid, l, r), get_add(rch, mid+1, end, l, r));
    }
    LL get_sub(int p, int beg, int end, int l, int r)
    {
        if(end < l || beg > r)
            return pinf;
        if(beg >= l && end <= r)
            return sub[p];
        int mid = (beg + end) / 2;
        int lch = p * 2, rch = p * 2 + 1;
        return max(get_sub(lch, beg, mid, l, r), get_sub(rch, mid+1, end, l, r));
    }
    void update(int p, int beg, int end, int pos, LL k)
    {
        if(beg == end)
        {
            add[p] = k + C * beg;
            sub[p] = k - C * beg;
            return;
        }
        int mid = (beg + end) / 2;
        int lch = p * 2, rch = p * 2 + 1;
        pos <= mid ? update(lch, beg, mid, pos, k) : update(rch, mid+1, end, pos, k);
        push_up(p);
    }
private:
    LL C;
    vector<LL> add, sub;
    void push_up(int p)
    {
        int lch = p * 2, rch = p * 2 + 1;
        add[p] = max(add[lch], add[rch]);
        sub[p] = max(sub[lch], sub[rch]);
    }
};

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    int N;
    LL C;
    cin >> N >> C;
    vector<LL> now(N+1, pinf);
    now[1] = 0;
    SegmentTree t(N, C);
    int M, T;
    LL P;
    cin >> M;
    while(M--)
    {
        cin >> T >> P;
        LL val = max(t.get_add(1, 1, N, 1, T) - C * T, t.get_sub(1, 1, N, T, N) + C * T) + P;
        if(val > now[T])
        {
            now[T] = val;
            t.update(1, 1, N, T, now[T]);
        }
    }
    cout << *max_element(now.begin(), now.end()) << endl;
    return 0;
}


最后更新: 2024-06-01
创建日期: 2024-05-22