首页 > 其他分享 >「Luogu P2501」[HAOI2006] 数字序列

「Luogu P2501」[HAOI2006] 数字序列

时间:2024-11-26 14:48:53浏览次数:5  
标签:拔高 HAOI2006 int Luogu P2501 修改 序列 阶梯 sim

题目

现在我们有一个长度为 \(n\) 的整数序列 \(a\)。但是它太不好看了,于是我们希望把它变成一个单调严格上升的序列。但是不希望改变过多的数,也不希望改变的幅度太大。

Luogu

分析

首先考虑 subtask 1: 最小化要修改的数。

如果正面思考一个数修改或不修改的答案,会发现答案的更新依赖于前一个数具体的修改方案,问题就变得非常棘手。这时不妨反过来考虑,什么样的数可以不修改?假设我们保留 \(a_{i}\) 和 \(a_{j}\) 不变,其中 \(i<j\),根据条件必然有 \(a_{j} - a_{i} \geq j - i\), 移项之后可以得到 \(a_{j}-j\geq a_{i}-i\), 在 \(a\) 数组中所有邻项分别满足这个不等式子序列就是可以不修改的子序列,令 \(b_{i}=a_{i}-i\),于是问题转化为了求 \(b\) 数组的最长不下降子序列,这个直接套公式就可以了。

然后考虑 subtask 2: 最小化改变量的绝对值。

修改 \(a\) 为严格上升序列相当于修改 \(b\) 为不下降序列,不妨直接在 \(b\) 上修改。设 \(b_{l}\) 与 \(b_{r}\) 是求出的最长不下降子序列中相邻的两个数,我们的任务是修改 \(l+1 \sim r-1\) 这部分的数。直接 DP 枚举可能的取值仍然依赖于前一个数的具体值,非常麻烦,不妨先随便选一个合法的方案然后试着对它做修改来尽可能减小修改量。对于这种序列大小相关的题,常将其看作为柱状图、折线图等来视觉化方便进一步思考,这里邻项可以相等的情况不妨考虑下列阶梯图的形式:

[1]

从图中很容易看出,如果一个阶梯上的点,位于其上方的点和下方的点数目一致的话(方便起见,记这个条件为 \(A\)),那么这个阶梯上下移动的过程中这些点修改量之和都不会变化,而阶梯的高度必然是递增的,此时我们可以想一个极端情况:将 \(l+1 \sim r-1\) 所有数都修改为 \(b_{l}\), 然后依次考虑拔高 \(i \sim r-1\) 表示的阶梯。显然一开始的点若小于等于 \(b_{l}\) 的话肯定不会拔高这个阶梯,若 \(b_{i} > b_{l}\),我们考虑拔高 \(i \sim r-1\), 显然只有拔高到能使这个阶梯满足 \(A\) 时才是最优,如果不管怎么拔高都无法满足 \(A\) 的话说明一开始就低于阶梯的点要多于高于阶梯的点,拔高只会使答案更大,因此此时就保持不变继续考虑下一个点直到能满足 \(A\). 假设在点 \(m\) 处成功通过拔高 \(m \sim r-1\) 的阶梯满足了 \(A\), 就会发现此时已经是最优解了:将上图旋转 90° 看,\(m \sim r - 1\) 这一段的答案就是将阶梯左边的点与右边的点任意两两配对得到的线段长度之和,显然这些点的答案之和不可能小于这个值,因此我们只要找到第一个能满足 \(A\) 的点即可得到答案,恰好是左右两段阶梯(不包括 \(b_{r}\) 的话)。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

int gi() { // 还在用 gi() 是因为这是 23 年的代码(
    int x = 0, f = 1; char c = getchar();
    for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
    for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
    return x * f;
}

const int N = 35005;
const int INF = 1e9;

int n;
ll f[N], pre[N], suf[N];
int a[N], b[N], len[N], dp[N];

vector <int> vec[N];

int main() {
    n = gi();
    for (int i = 1; i <= n; ++i) a[i] = gi(), b[i] = a[i] - i;
    b[0] = -INF, b[n + 1] = INF;
    int mx = 0;
    for (int i = 1; i <= n + 1; ++i) {
        int l = 1, r = mx;
        while (l <= r) {
            int mid = l + r >> 1;
            if (len[mid] <= b[i]) l = mid + 1;
            else r = mid - 1;
        }
        dp[i] = l, len[l] = b[i];
        vec[l].push_back(i);
        mx = max(mx, l);
    }
    mx--;
    vec[0].push_back(0);
    memset(f, 0x3f3f3f3f, sizeof f);
    f[0] = 0;
    for (int i = 1; i <= n + 1; ++i) {
        for (int j = 0; j < vec[dp[i] - 1].size(); ++j) {
            int from = vec[dp[i] - 1][j];
            if (b[from] > b[i] || from > i) continue;
            pre[from] = 0, suf[i] = 0;
            for (int k = from + 1; k < i; ++k) pre[k] = pre[k - 1] + abs(b[k] - b[from]);
            for (int k = i - 1; k > from; --k) suf[k] = suf[k + 1] + abs(b[k] - b[i]);
            for (int k = from; k < i; ++k) f[i] = min(f[i], f[from] + pre[k] + suf[k + 1]);
        }
    }
    printf("%d\n%lld\n", n - mx, f[n + 1]);
    return 0;
}

  1. 图片来自 洛谷用户:学委 的题解 ↩︎

标签:拔高,HAOI2006,int,Luogu,P2501,修改,序列,阶梯,sim
From: https://www.cnblogs.com/huangliwen/p/18570135

相关文章

  • 「Luogu P3953」[NOIP2017 提高组] 逛公园
    题目策策同学特别喜欢逛公园。公园可以看成一张\(N\)个点\(M\)条边构成的有向图,且没有自环和重边。其中\(1\)号点是公园的入口,\(N\)号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。策策每天都会去逛公园,他总是从\(1\)号点进去,从\(N\)号点......
  • 「Luogu P5441」【XR-2】伤痕
    人类智慧题,然而我是蒟蒻qwq.题目X国经历了一场前所未有的大地震,人们伤痕累累,整个国家破碎不堪。为了帮助人们痊愈,也为了让X国能够生存下去,X国国王决定重建X国。国王决定先建造\(n\)座城市,由于国王喜欢奇数,所以\(n\)为奇数。城市建造完后,需要给每两座城市之间都......
  • 「Luogu P4516」[JSOI2018] 潜入行动
    题目外星人又双叒叕要攻打地球了,外星母舰已经向地球航行!这一次,JYY已经联系好了黄金舰队,打算联合所有JSOIer抵御外星人的进攻。在黄金舰队就位之前,JYY打算事先了解外星人的进攻计划。现在,携带了监听设备的特工已经秘密潜入了外星人的母舰,准备对外星人的通信实施监听。外星......
  • 每日一题:https://www.luogu.com.cn/problem/P1106
    题目链接:https://www.luogu.com.cn/problem/P1106#include<iostream>#include<string>usingnamespacestd;intmain(){intn,k,mu;stringnum;intt=1,wei,ti=0;;intarr[260];boolyes=0;cin>>num>>k;n=num.l......
  • [luoguP1903] 数颜色
    题意原题链接给定序列\(a\),每次查询一个区间\([l,r]\)中有多少个不同的数,或进行单点修改。sol如果不修改的话,本题就是普通莫队[luoguSP3267]D-query由于有修改,所以需要再增加一维,记录这次查询是在多少次修改以后查询的,然后在莫队代码后面添加对修改次数的处理,即暴力修改......
  • [luoguP11324] Speaker
    题意原题链接给定一个带权无根树,第\(i\)个节点上有一个数\(c_i\),每次询问给定两个点\(x,y\),在无根树上任选一点\(z\),使\(c_x+c_y+c_z-dist(x,z)-dist(z,y)\)最大,输出最大的值。sol考虑\(z\)可能有两种情况,要么是\(x\toy\)的路径上的一点\(t\),要么从路径上的一点......
  • [luoguP11323] Happy Card
    题意原题链接有\(n\)种牌,第\(i\)种牌有\(a_i\)张,每次可以出\(1\)张(单牌)、\(2\)张(对子)或\(4\)张相同的牌(四炸),或是\(3\)张相同的牌及\(1\)张不同的牌(三带一),求把牌出完最少需要多少次。sol赛时看到这道题,就想到了[luoguP2668]斗地主,由于没有顺子,因此可以直接考虑......
  • [luoguSP3267] D-query
    题意给定\(n\)个数\(a_1\cdotsa_n\)和\(q\)个查询,每次查询区间\([l,r]\)中,\(a\)的不同数的个数sol本题不强制在线,因此可以考虑离线算法,如莫队。莫队是一个非常神奇的算法,它的基本思想是:将区间进行某种排序后,通过移动\(l,r\)指针,每次移动时处理答案的增减来得到答......
  • [lnsyoj1469/luoguP4644] Cleaning Shifts
    题意原题链接给定\(n\)个区间\([a_i,b_i]\),第\(i\)个区间拥有权值\(S_i\),求使用这些区间将区间\([M,E]\)(包含所有\(n\)个区间)完全覆盖(两端点不需要重合)所需区间的权值最小值。sol一道板子题,本来是数据结构优化DP,但是被最短路薄纱了。考虑将每一个时间点视作一个节......
  • 【GESP】C++一级练习 luogu-B2060, 满足条件的数累加
    一级知识点循环和取余操作练习题,基础练习。题目题解详见:https://www.coderli.com/gesp-1-luogu-b2060/【GESP】C++一级练习luogu-B2060,满足条件的数累加|OneCoder一级知识点循环和取余操作练习题,基础练习。https://www.coderli.com/gesp-1-luogu-b2060/......