cfRound170D题

https://codeforces.com/contest/2025/problem/D

题目大意:n个操作(ri>0智力测试操作,ri=0发放属性点,ri<0力量测试操作),属性点发放操作为m个,问至多能通过过少个测试.

考虑一个朴素做法dp[i][j]表示前i个操作,智力点为j时能通过最多的测试.假设当前总属性点为s

  1. 如果ri==0,dp[i][j]=std::max(dp[i-1][j],dp[i-1][j-1]),这表示两个选择,当前是第i个操作,且为分配属性点操作,如果把当前属性点分配给智力,那么上一个智力应当是j-1(dp[i][j]规定了当前智力点为j,所以上一个它应当由j-1转移而来).如果把属性点分配给力量,智力属性点应当不变
  2. 如果ri>0,对于所有j>=ri都有dp[i][j]=dp[i-1][j]+1,否则为dp[i-1][j]
  3. 同理,力量测试也是,s-j>=|ri|时dp[i][j]=dp[i-1][j]+1,否则dp[i-1][j]

但是对于这道题来说,这样的做法不管是时间复杂度还是空间复杂度都是不被允许的.

不妨先降低空间复杂度,我们可以将dp前面一个维度删除掉,只保留后面一个维度,及dp[i]表示智力点为i时能通过最多的测试,新建一个f数组,f[i]用来记录到下一个加属性点操作的之前智力为i能通过多少测试的差分(只有这样才能在O1的时间复杂度之内完成增减的操作)

每次读入ri,如果ri不为0,这告诉我们的第一个信息是它是哪一种测试,第二个信息是当前属性点数固定,我们既知道是什么测试了又知道现在属性点有多少,还知道当前测试要求多少属性点,我们是不是知道哪些情况下可以通过这个测试,比如当前是智力测试,当分配给智力的属性点大于这个测试的要求就能通过吧.这些能通过测试的属性分配情况都是连起来的,我们可以直接用差分数组f在o1的时间复杂度内完成这个操作.

现在考虑ri为0的情况,在朴素做法中,我们分析了当ri为0的时候,我们要对dp[i-1][j]和dp[i-1][j-1]取max,而其他情况其实都是垂直向下递推的,现在我们只有一个数组dp[i],试想一下,如果正着递推会产生什么结果,

可以看到上面这张图,我们5=1,6=max(1,2),7=max(2,3),8=max(3,4),但是现在我们只有一个数组(当然你可以用两个数组),当我们求出6了以后,6是不是会覆盖掉2,那么再次求7的时候,就变成了max(6,3)这是不对的,我们想要的是max(2,3),怎么解决呢?我们可以倒着求,比如先求8=max(3,4),好了,现在4变成8了,然后在求7,可以发现7=max(2,3)它不会用到4,没有产生影响,所以我们倒着求就解决了这个问题.

下面贴出代码

cpp 复制代码
using ll = long long;

//d[i][I]表示处理完前i个记录后,当前智力水平为I时的结果,力量水平S=P-I,P是总共获得的点数
//如果ri==0,dp[i][I]=std::max(dp[i-1][I-1],dp[i-1][I])
//如果最后一个记录ri>0,对于所有I>=ri的情况(智力指数大于ri的情况下)d[i][I]=d[i-1][I]+1,否则dp[i][I]=dp[i-1][I]
//
//如果ri<0对于所有I<=P+ri,dp[i][I]=dp[i-1][I]+1,否则dp[i][I]=dp[i-1][I]
//一共有n个记录,m次加点,dp[i]表示前i组记录的最大值,假设到第i组记录有j个属性点
//dp[i]应该表示智力点为i时的最大值即d[{1-n}][i]的最大值
//假设
//第二种情况和第三种情况是区间加法问题
int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    //n个测试或给点,m个点
    
    int n, m;
    std::cin >> n >> m;
    //
    std::vector<int> dp {0};//dp[j]表示智力为j时获得的最大总点数
    std::vector<int> f {0};//f作为辅助数组(差分数组)来记录懒标记,方便处理区间加法
    //f的长度表示智力点数
    int s = 0;//s表示点数
    //输入每一个测试
    for (int i = 0; i < n; i++) {
        int r;
        std::cin >> r;//输入测试
        //如果是智力测试
        if (r > 0) {
            //
            if (r < f.size()) {
                f[r]++;//I<r的情况都不加,因为I=f.size()表示智力,智力如果小于测试点都过不了,所以加到大于测试点的地方
            }
        }//如果是力量测试
        else if (r < 0) {
            r = -r;//先换成正的
            r = s - r;//现在是力量点数
            //r是需要消耗的力量点数,那么s-r就是至少预留的力量点数
            //这么思考,在[0,f.size()]之间的某个I,如果想要通过力量测试,那么s-I>=r,即s-r>=I,也就是I的最大取值就是s-r
            //哦,f.size()是s的长度,所以r+1是不会超过数组长度的,
            if (r >= 0) {
                f[0]++;
                f[r + 1]--;
            }
        }
        else {
            //如果智力点固定,那么能通过的测试数也是固定的,现在新增了一个属性点,那么就有两种情况了
            //dp[j]表示dp[i][j]中取一个能通过点的最大情况
            //如果是点数
            //f[j]表示智力点j能通过的情况,
            for (int j = 1; j <= s; j++) {
                f[j] += f[j - 1];//将点数全部加起来
            }
            for (int j = 0; j <= s; j++) {
                dp[j] += f[j];
            }
            dp.push_back(0);
            s++;
            for (int j = s; j >= 1; j--) {
                //这个表示两种选择,当智力点为j时,要么从dp[i-1][j-1]转移过来,要么从将属性点加给力量则不转移
                //
                dp[j] = std::max(dp[j], dp[j - 1]);
            }
            f.assign(s + 1, 0);
        }
    }
    for (int j = 1; j <= s; j++) {
        f[j] += f[j - 1];
    }
    for (int j = 0; j <= s; j++) {
        dp[j] += f[j];
    }

    int ans = *std::max_element(dp.begin(), dp.end());
    std::cout << ans << "\n";

    return 0;
}
相关推荐
Jasmine_llq8 分钟前
《 火星人 》
算法·青少年编程·c#
闻缺陷则喜何志丹19 分钟前
【C++动态规划 图论】3243. 新增道路查询后的最短距离 I|1567
c++·算法·动态规划·力扣·图论·最短路·路径
Lenyiin38 分钟前
01.02、判定是否互为字符重排
算法·leetcode
鸽鸽程序猿1 小时前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列
Jackey_Song_Odd1 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
乐之者v1 小时前
leetCode43.字符串相乘
java·数据结构·算法
A懿轩A2 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
古希腊掌管学习的神2 小时前
[搜广推]王树森推荐系统——矩阵补充&最近邻查找
python·算法·机器学习·矩阵