题解:CF1912A Accumulator Apex

Luogu - CF1912A

思路

考虑贪心,看到这道题的第一反应该是对原始数列进行合并,把相邻的正数合并在一起,把相邻的负数合并在一起。
这样每个数列都变为一正一负、一正一负……的形式。

可能会想到先吃每个数列开头的正数,然后按找负数排序,每次都找绝对值最小的那个负数吃。

但这样是不对的,假设当前 x = 30 x=30 x=30,一个数列为 [ − 15 , 45 ] [-15,45] [15,45],另一个为 [ − 32 ] [-3 2] [32],按照上面的策略,应该选第二个数列,但显然选第一个数列才是最优的。

还有一种情况,假设 x = 3 x=3 x=3,只有一个数列 [ − 3 , 1 ] [-3,1] [3,1],如果盲目选它的话会导致原来的 x = 3 x=3 x=3 变成 x = 1 x=1 x=1,收益不增反降。

正确思路为对每一个数列都求前缀和 s s s,每次取完数字后找到 当前取数位置后第一个前缀和大于 0 0 0 的位置(即第一个 s i > 0 s_i>0 si>0),那个位置就是我们要达到的目标(因为前缀和大于 0 0 0 意味着收益为正,我们可以回本)。

根据上面,可以把每一个数列按照前缀和分成若干段,保证每一段的结尾都是前一段结束后的第一个前缀和大于 0 0 0 的位置。

由此,对每个数列分成的每一段赋予其一个数对 ( a , b ) (a,b) (a,b),表示可以付出 a a a 的代价,获得 b b b 的收益。

  • 代价应该是当前段的最小前缀和(因为如果有多个负数的话,只要考虑最小的那一个——也就是影响最大的那一个就行), b b b 就是第一个大于 0 0 0 的前缀和值。

  • 注意此时的 a a a 应该小于等于 0 0 0(当数列第一个数就为正数时, a = 0 a=0 a=0), b b b 应该大于 0 0 0,这样才符合代价-贡献的基本要求。如果所有 b b b 都小于 0 0 0,说明当前 x x x 已经为最优,不必继续计算。

根据上面的数对,用优先队列排序后(第一关键字按照 a a a 的绝对值升序排序,因为代价越小越好;第二关键字按照 b b b 降序排序,因为贡献越大越好)模拟取数过程:

  1. 找到当前的队头,判断当前的 x x x 能否获取。
    • 如果能,那么更新当前的 x x x,继续计算前缀和,找到第一个大于 0 0 0 的前缀和继续放入优先队列。
    • 不能取的话,不做处理。
  2. 弹出当前的队头,因为它已经在步骤 1 1 1 中使用过了(因为我们排过序了,此时的队头一定是最优的,无法取出的话直接出队即可)。

设所有列表长度之和为 n n n,则时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

#include <climits>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
typedef long long ll;

struct P
{
    ll a, b; // 付出a的代价,获得b的收益 a<=0,b>0
    int id; // 表示在a数组中的编号
    friend bool operator < (const P &p1, const P &p2)
    {
        if (abs(p1.a) != abs(p2.a))
            return abs(p1.a) > abs(p2.a); // 默认按照 绝对值 升序排序
        // 原来是abs(p1.a)<abs(p2.a),但因为优先队列默认大顶堆,所以要反过来
        
        return p1.b < p2.b; // 否则按照收益 降序排序,即收益越大越靠前
        // 原来是p1.b > p2.b,但因为优先队列默认大顶堆,所以要反过来
    }
};

void solve()
{
    int x, k;
    cin >> x >> k;
    vector< vector<int> > a(k + 1); // 存储所有数列
    vector <int> pos(k + 1); // 每个数列的指针,表示当前前缀和统计到哪个位置了
    vector< vector<ll> > s(k + 1); // 前缀和数组

    for (int i = 1, y; i <= k; i ++)
    {
        cin >> y;
        a[i].resize(y + 1), s[i].resize(y + 1);
        for (int j = 1; j <= y; j ++) cin >> a[i][j];
    }

    priority_queue <P> Q; // 存储代价和贡献的队列
    for (int i = 1; i <= k; i ++)
    {
        ll minn = a[i][1]; // 存储过程中的最小前缀和
        for (int j = 1; j < a[i].size(); j ++)
        {
            s[i][j] = s[i][j - 1] + a[i][j];
            minn = min(minn, s[i][j]);
            if (s[i][j] > 0)
            {
                Q.push({minn, s[i][j], i});
                pos[i] = j;
                break;
            }
        }
    }

    ll temp = x; // 替代变量
    while (!Q.empty())
    {
        P it = Q.top(); Q.pop();
        if (temp + it.a >= 0) // 注意a此时应为<=0的数
        {
            temp += it.b; // 直接加上收益即可
            int i = it.id;
            ll minn = s[i][pos[i]];
            for (int j = pos[i] + 1; j < a[i].size(); j ++)
            {
                if (j == pos[i] + 1) s[i][j] = a[i][j]; // 注意要以大于0的前缀和为界分段,所以计算每一段的前缀和是独立的,不能接着s[i][j-1]计算。要单独开一个新前缀和
                else s[i][j] = s[i][j - 1] + a[i][j];
                minn = min(minn, s[i][j]);

                if (s[i][j] > 0)
                {
                    Q.push({minn, s[i][j], i});
                    pos[i] = j;
                    break;
                }
            }
        }
    }
    cout << temp << endl;
}

signed main()
{
    ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}
  • 20
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!对于扫雷游戏的题解,我可以给你一些思路和代码示例。首先,你需要了解扫雷游戏的规则和要求。接下来,你可以使用C++语言来实现游戏逻辑和界面。 下面是一个简单的扫雷游戏的C++代码示例: ```cpp #include <iostream> #include <vector> #include <random> using namespace std; class MinesweeperGame { private: int rows; int cols; vector<vector<char>> board; vector<vector<bool>> revealed; vector<pair<int, int>> directions = {{-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}}; public: MinesweeperGame(int m, int n, int mineCount) { rows = m; cols = n; board.resize(rows, vector<char>(cols, ' ')); revealed.resize(rows, vector<bool>(cols, false)); placeMines(mineCount); calculateNumbers(); } void printBoard() { cout << " "; for (int j = 0; j < cols; j++) { cout << j << " "; } cout << endl; for (int i = 0; i < rows; i++) { cout << i << " |"; for (int j = 0; j < cols; j++) { cout << board[i][j] << "|"; } cout << endl; } } bool isGameOver() { for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (board[i][j] == 'M' && revealed[i][j]) { return true; } } } return false; } void reveal(int row, int col) { if (row < 0 || row >= rows || col < 0 || col >= cols || revealed[row][col]) { return; } revealed[row][col] = true; if (board[row][col] == 'M') { return; } if (board[row][col] == '0') { for (auto dir : directions) { reveal(row + dir.first, col + dir.second); } } } private: void placeMines(int mineCount) { random_device rd; mt1

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值