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;
}
相关推荐
jiao_mrswang34 分钟前
leetcode-18-四数之和
算法·leetcode·职场和发展
qystca42 分钟前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱43 分钟前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
今天吃饺子1 小时前
2024年SCI一区最新改进优化算法——四参数自适应生长优化器,MATLAB代码免费获取...
开发语言·算法·matlab
是阿建吖!1 小时前
【优选算法】二分查找
c++·算法
王燕龙(大卫)1 小时前
leetcode 数组中第k个最大元素
算法·leetcode
不去幼儿园2 小时前
【MARL】深入理解多智能体近端策略优化(MAPPO)算法与调参
人工智能·python·算法·机器学习·强化学习
Mr_Xuhhh2 小时前
重生之我在学环境变量
linux·运维·服务器·前端·chrome·算法
盼海3 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou6 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot