跳转至

ABC379(A-G)

A - Cyclic

题目大意

给你一个三位数 \(N\),其中 \(N\) 的数位中没有 \(0\)。设 \(a\)\(b\)\(c\) 分别代表 \(N\) 的百位、十位和个位数字。输出 \(bca\)\(cab\)

参考代码
#include <iostream>
#include <string>

using namespace std;

int main()
{
    string s;
    cin >> s;
    cout << s[1] << s[2] << s[0] << ' ' << s[2] << s[0] << s[1] << endl;
    return 0;
}

B - Strawberries

题目大意

你有 \(N\) 个牙齿排成一行,牙齿的健康状况用一个长度为 \(N\) 的字符串 \(S\) 代表。如果 \(S_i\)o,表示第 \(i\) 个牙齿是健康的。如果 \(S_i\)x,表示第 \(i\) 个牙齿是不健康的。当你有连续的 \(K\) 个健康的牙齿时,可以用这 \(K\) 个牙齿吃草莓,吃完草莓之后,这连续的 \(K\) 个牙齿就会变成不健康的牙齿。问:最多能吃几个草莓?

解题思路

贪心,一定是优先用靠近左边的牙齿最优,直接遍历即可。

参考代码
#include <iostream>
#include <string>

using namespace std;

int main()
{
    int n, k;
    cin >> n >> k;
    string s;
    cin >> s;
    int cnt = 0, ans = 0;
    for (auto c : s)
    {
        if (c == 'O')
        {
            if (++cnt == k)
            {
                ans++;
                cnt = 0;
            }
        }
        else
            cnt = 0;
    }
    cout << ans << endl;
    return 0;
}

C - Sowing Stones

题目大意

\(N(2 \le N \le 2 \times 10 ^ 9)\) 个格子和 \(M(1 \le M \le 2 \times 10 ^ 5)\) 堆石头,第 \(i\) 堆石头在第 \(X_i\) 个格子中,有 \(A_i\) 个。

你可以执行以下操作任意次:

  • 选择第 \(i(1 \le i \le N-1)\) 个格子,如果第 \(i\) 个格子上有石头,则移动 \(1\) 个石头到第 \(i+1\) 个格子中。

问:至少要操作几次才能让 \(N\) 个格子都恰好只有 \(1\) 个石头?如果无论如何也无法做到,输出 -1

解题思路

因为石头只能向右移动,所以一定是从右往左填充每个格子的石头。但是因为有 \(N\) 个格子,必须将连续的一段一起处理才能加速。考虑我们找到了连续的某段 \([l, r]\) 目前没有石头,则需要在已有的石头堆中找到一个小于 \(l\) 的格子,由这个格子补充 \([l, r]\) 的石头,操作的次数可以用等差数列求和公式算出来。其余的细节就是考虑如何无解的判定,有两种情况:

  • 石头不够的情况。
  • 石头过多的情况。也就是 \([l, r]\) 处理完成后,负责补充这一段的石头剩余的石头大于 \(1\) 个,就会导致石头数量过多。
参考代码
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

using namespace std;
using LL = long long;

int main()
{
    int n, m;
    cin >> n >> m;
    vector<pair<int, int>> stone(m);
    for(int i = 0; i < m; i++)
        cin >> stone[i].first;
    for(int i = 0; i < m; i++)
        cin >> stone[i].second;
    sort(stone.begin(), stone.end());
    LL ans = 0;
    int now = n, top = m - 1;
    while(now && top >= 0)
    {
        LL cnt = stone[top].second;
        if(cnt > now - stone[top].first + 1) 
        {
            cout << -1 << endl;
            return 0;
        }
        LL r = now - stone[top].first;
        LL l = r - cnt + 1;
        ans += cnt * (l + r) / 2;
        now -= cnt;
        top--;
    }
    if(now > 0)
        ans = -1;
    cout << ans << endl;
    return 0;
}

D - Home Garden

题目大意

你需要维护一个集合,给你 \(Q(1 \le Q \le 2 \times 10 ^ 5)\) 个操作,有三种类型的操作:

  • 1:给集合中添加一个数字 \(0\),注意,集合中允许出现相同的数字。
  • 2 T:给集合中所有的数字的值加上 \(T(1 \le T \le 10 ^ 9)\)
  • 3 H:删除集合中所有大于等于 \(H\) 的数字,然后输出删除了多少个数字。
解题思路

集合中所有数字加 \(T\) 相当于后面加入的数字基准减少 \(T\),同理操作 3 的基准也要减少 \(T\)

这样只需要维护一个可重集(multiset)就可以了,因为每个点只会加入和删除一次,时间复杂度就是 \(O(Q\log Q)\),不过更简单的是,注意到加入的数字一定是递减的,所以维护一个队列就可以。

参考代码
#include <iostream>
#include <queue>

using namespace std;
using LL = long long;

int main()
{
    int q;
    cin >> q;
    LL add = 0;
    queue<LL> queue;
    while(q--)
    {
        int op;
        cin >> op;
        if(op == 1)
            queue.push(-add);
        else if(op == 2)
        {
            LL t;
            cin >> t;
            add += t;
        }
        else
        {
            LL h;
            cin >> h;
            int ans = 0;
            while(!queue.empty() && queue.front() >= h - add)
            {
                queue.pop();
                ans++;
            }
            cout << ans << endl;
        }
    }
    return 0;
}

E - Sum of All Substrings

题目大意

给你一个长度为 \(N(1 \le N \le 2 \times 10 ^ 5)\) 的字符串 \(S\)\(S\) 由数字 19 组成。定义 \(f(i, j)\) 表示 \(S\) 的子串 \(S_iS_{i+1}\cdots S_j\) 所对应的数字,求出 \(\sum\limits_{i = 1}^N\sum\limits_{j=i}^Nf(i, j)\) 的值。

解题思路

首先模拟一下第一个样例 379,假设我们固定 \(r\),计算的过程就是:

3
37 + 7
379 + 79 + 9

\(g[r] = \sum\limits_{i = 1}^r f(i, r)\),则有 \(g[r] = g[r-1] * 10 + r * S[i]\),这样的话,看上去只需要写一个高精度就好了,但是数字太大,高精度也会 TLE。我试过避开高精度乘法,也就是将 \(g[r-1] * 10\) 拆成字符串后面补 '0',以及 \(r * S[i]\) 拆成 to_string,即使这样,在只剩下高精度加法的情况下还是会 TLE。可能压位高精能卡过去。

正确的做法是,考虑每个数字的贡献,显然第 \(i\) 位只会影响 \(i\) 及以后的值。还是以刚刚的 379 为例:

3
37 + 7
379 + 79 + 9

第一个数字产生的贡献是 \(333\),第二个数字产生的贡献是 \(77 + 77\),第三个数字产生的贡献是 \(9 + 9 + 9\),每个数字的贡献就是 \(i S[i]\times(10^0 + 10^1 + \cdots + 10^{N-i})\),这里我们应该存所有 \(10^k\) 的贡献,这样剩下的部分 \(iS[i]\) 是很小的,用 long long 就存的下。只需要用差分数组就能维护,最后再把所有的贡献合并起来就可以了。

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

using namespace std;

int main()
{
    int n;
    string s;
    cin >> n >> s;

    auto add = [](const string &x, const string &y) -> string
    {
        int n = x.size(), m = y.size();
        string z(max(n, m), '0');
        int i = n - 1, j = m - 1, k = z.size() - 1;
        int in = 0;
        for (; i >= 0 && j >= 0; i--, j--, k--)
        {
            int sum = (x[i] - '0') + (y[j] - '0') + in;
            in = sum / 10;
            sum %= 10;
            z[k] = sum + '0';
        }
        for (; i >= 0; i--, k--)
        {
            int sum = (x[i] - '0') + in;
            in = sum / 10;
            sum %= 10;
            z[k] = sum + '0';
        }
        for (; j >= 0; j--, k--)
        {
            int sum = (y[j] - '0') + in;
            in = sum / 10;
            sum %= 10;
            z[k] = sum + '0';
        }
        return in ? '1' + z : z;
    };

    string ans = {s[0]}, g = ans;
    for (int i = 1; i < n; i++)
    {
        int c = s[i] - '0';
        g += '0';
        string ic = to_string((i+1) * c);
        g = add(g, ic);
        ans = add(ans, g);
    }
    cout << ans << endl;
    return 0;
}

F - Buildings 2

题目大意

\(N(2 \le N \le 2 \times 10 ^ 5)\) 个建筑排成一行,第 \(i\) 个建筑的高度是 \(H_i(1 \le H_i \le N)\)。对于数对 \((i, j)(1 \le i < j \le N)\),建筑 \(i\) 能看到建筑 \(j\) 的条件是不存在建筑 \(k\) 满足 \(i < k < j\)\(H_k > H_j\)

给你 \(Q(2 \le Q \le 2 \times 10 ^ 5)\) 个询问,每一个询问给你两个数字 $(l_i, r_i) $,其中 \((l_i < r_i)\),你需要回答出在建筑 \(r_i\) 的右边有多少个建筑既能被建筑 \(l_i\) 看到,也能被建筑 \(r_i\) 看到。

解题思路

假设在建筑 \(r_i\) 的右侧,第一个能同时被建筑 \(l_i\) 和建筑 \(r_i\) 看到的是建筑 \(x\),后续能看的建筑一定是从建筑 \(x\) 开始高度递增的建筑,也就是从建筑 \(x\) 开始一直跳到右侧第一个高度大于等于自身的建筑。显然这个信息可以预处理一个单调栈,从后往前处理一个高度单调递减的栈,栈的大小就是每个元素能往后跳多少步。

下面考虑对于一个 \((l_i, r_i)\),如何找到第一个能同时被建筑 \(l_i\) 和建筑 \(r_i\) 看到的是建筑 \(x\)。建筑 \(l_i\) 会被 \([l_i+1, r_i]\) 范围内的所有建筑遮挡视线,进一步考虑,实际是 \([l_i+1, r_i]\) 范围内最高的建筑遮挡了 \(l_i\),设 \([l_i+1, r_i]\) 范围内最高的建筑是建筑 \(k\),则建筑 \(l_i\) 和建筑 \(r_i\) 能同时看到的建筑就是在建筑 \(k\) 右侧的比建筑 \(k\) 还高的建筑,而这已经用单调栈解决了。至于如何求出某个区间内最高的建筑,用 ST 表即可。

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

using namespace std;

struct SparseTable
{
    int n;
    const vector<int> &h;
    vector<int> lg;
    vector<vector<int>> max_idx;

    SparseTable(const vector<int> &h) : n(h.size()), h(h), lg(n + 1)
    {
        for (int i = 2; i <= n; i++)
            lg[i] = lg[i >> 1] + 1;
        max_idx.assign(n, vector<int>(lg[n] + 1));
        for (int i = 0; i < n; i++)
            max_idx[i][0] = i;
        for (int k = 1, t = 1; k <= lg[n]; k++, t <<= 1)
            for (int i = 0, j = t; j + t <= n; i++, j++)
            {
                int x = max_idx[i][k - 1], y = max_idx[j][k - 1];
                max_idx[i][k] = h[x] > h[y] ? x : y;
            }
    }

    int RMQ(int l, int r) const
    {
        int k = lg[r - l + 1], len = 1 << k;
        int x = max_idx[l][k], y = max_idx[r - len + 1][k];
        return h[x] > h[y] ? x : y;
    }
};

int main()
{
    int n, q;
    cin >> n >> q;
    vector<int> h(n);
    for (int i = 0; i < n; i++)
        cin >> h[i];
    SparseTable st(h);
    vector<int> cnt(n);
    stack<int> stack;
    for (int i = n - 1; i >= 0; i--)
    {
        while (!stack.empty() && h[i] > h[stack.top()])
            stack.pop();
        cnt[i] = stack.size();
        stack.push(i);
    }
    while (q--)
    {
        int l, r;
        cin >> l >> r;
        l--, r--;
        int k = st.RMQ(l + 1, r);
        cout << cnt[k] << endl;
    }
    return 0;
}

G - Count Grid 3-coloring

题目大意

给你一个 \(H \times W(1 \le H \times W \le 200)\) 的网格,每个格子的字符有 123?,你需要将所有的 ? 替换成 123,问:有多少种替换方案能够让任意相邻的位置的数字都不相同?答案对 \(998244353\) 取模。

解题思路

一眼看上去就是一个状压的模板,看一眼数据范围,\(H \times W \le 200\)\(200 = 10 \times 20\),也就是说列的最小值是 \(10\)(如果行比列多,旋转一下就好),这样每一行最多有 \(10\) 格,每一格需要 \(2\text{bit}\) 存储状态,也就是 \(2^{20}\) 个状态。

定义 \(dp[i][j][s]\) 表示填到第 \(i\) 行第 \(j\) 个时,最下方的轮廓线的状态为 \(s\) 的方案数。转移的时候,只需要根据当前格是 ? 还是数字算出下一个可行的状态就好。时间复杂度 \(O(HW2^{20})\),能过。

需要注意的是第一行的轮廓线状态的初始答案不太好直接求出来,我的办法是直接枚举 \(2^{20}\) 种情况暴力算出来。代码中的 dfs 函数就是在干这个事情。

参考代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

using namespace std;
const int MOD = 998244353;

int main()
{
    int n, m;
    cin >> n >> m;
    vector<vector<int>> g;
    if (m <= n)
    {
        g.assign(n, vector<int>(m));
        for (int i = 0; i < n; i++)
        {
            string s;
            cin >> s;
            for (int j = 0; j < m; j++)
                g[i][j] = s[j] == '?' ? -1 : (s[j] - '1');
        }
    }
    else
    {
        g.assign(m, vector<int>(n));
        for (int j = 0; j < n; j++)
        {
            string s;
            cin >> s;
            for (int i = 0; i < m; i++)
                g[m - i - 1][j] = s[i] == '?' ? -1 : (s[i] - '1');
        }
        swap(n, m);
    }
    unordered_map<int, int> dp[2];
    unordered_map<int, int> *now = &dp[0], *pre = &dp[1];

    auto dfs = [&now, &g](auto &self, int cur, int st, int last) -> void
    {
        if (cur == -1)
        {
            (*now)[st] = 1;
            return;
        }
        if (g[0][cur] == -1)
        {
            for (int i = 0; i < 3; i++)
                if (last != i)
                    self(self, cur - 1, st << 2 | i, i);
        }
        else
        {
            int x = g[0][cur];
            if (last != x)
                self(self, cur - 1, st << 2 | x, x);
        }
    };

    auto get = [](int s, int i) -> int
    { return (s >> (2 * i) & 0b11); };

    auto set = [](int s, int i, int x) -> int
    { return (s & ~(0b11 << (2 * i))) | x << (2 * i); };

    dfs(dfs, m - 1, 0, -1);

    for (int i = 1; i < n; i++)
    {
        for (int j = 0; j < m; j++)
        {
            swap(now, pre);
            now->clear();
            int x = g[i][j];
            if (x == -1)
            {
                for (auto [s, cnt] : *pre)
                {
                    int up = get(s, j), left = j ? get(s, j - 1) : -1;
                    for (int y = 0; y < 3; y++)
                        if (y != up && y != left)
                        {
                            int ns = set(s, j, y);
                            (*now)[ns] += cnt;
                            (*now)[ns] %= MOD;
                        }
                }
            }
            else
            {
                for (auto [s, cnt] : *pre)
                {
                    int up = get(s, j), left = j ? get(s, j - 1) : -1;
                    if (x != up && x != left)
                    {
                        int ns = set(s, j, x);
                        (*now)[ns] += cnt;
                        (*now)[ns] %= MOD;
                    }
                }
            }
        }
    }
    int ans = 0;
    for (auto [s, cnt] : *now)
        ans = (ans + cnt) % MOD;
    cout << ans << endl;
    return 0;
}


最后更新: 2024-12-26
创建日期: 2024-12-26