Codeforces713C-Sonya and Problem Wihtout a Legend(dp + 离散化)

本文介绍了解决 CodeForces 713C 题目的方法,通过离散化和动态规划技巧,将问题转换为求最小代价的非严格单调递增序列。详细阐述了状态定义、转移方程,并提供了完整代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

http://codeforces.com/problemset/problem/713/C

思路

a[i]非常大但是n很小,可以考虑离散化,将一维考虑成下标,那么可以先将数组排序成b[i]
有一个题目是求非严格的单调递增序列需要的代价:
状态表示:d[i][j], 第i个数为b[]中的第j个数需要的最小代价
转移方程:d[i][j] = min(d[i][j], d[i - 1][k] + |a[i] - b[j]|) k ≤ j;
其中依赖于这样一个性质:对一个数a[i],经过操作后一定是原序列中的某个数(可以考虑将若干个数转化为x,那么代价为|a[i] - x|,当x为这些数的中位数的时候代价最小,因此经过操作后一定是原序列中的某个数)
上面的问题解决了后,这道题要求的是一个严格递增的序列,考虑若a[i] < a[i + 1],那么有a[i] - i ≤ a[i + 1] - (i + 1)。即将每位数字减去下标,就转化成了非严格递增序列的最小代价问题

细节

本来状态是O(n^2),转移是O(n)的,但是实际上只要最小的d[i - 1][k],因此每次记录一下最小的d[i - 1][k]即可

代码

#include <iostream>
#include <cstring>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <queue>
#include <sstream>
#include <iomanip>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <deque>
#include <bitset>
#include <algorithm>
using namespace std;

#define PI acos(-1.0)
#define LL long long
#define PII pair<int, int>
#define PLL pair<LL, LL>
#define mp make_pair
#define IN freopen("in.txt", "r", stdin)
#define OUT freopen("out.txt", "wb", stdout)
#define scan(x) scanf("%d", &x)
#define scan2(x, y) scanf("%d%d", &x, &y)
#define scan3(x, y, z) scanf("%d%d%d", &x, &y, &z)
#define sqr(x) (x) * (x)

const int maxn = 3005;
const LL INF = 1e18;
LL a[maxn], b[maxn], pre[maxn], d[maxn][maxn];
int n;

int main() {
    scan(n);
    for (int i = 1; i <= n; i++) {
        scanf("%I64d\n", &a[i]);
        a[i] -= i;
        b[i] = a[i];
    }
    sort(b + 1, b + 1 + n);
    for (int i = 0; i <= n; i++) {
        pre[i] = INF;
        for (int j = 0; j <= n; j++) {
            d[i][j] = INF;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (i == 1) d[i][j] = min(d[i][j], abs(a[i] - b[j]));
            else d[i][j] = min(d[i][j], pre[j] + abs(a[i] - b[j]));
            pre[j] = min(pre[j - 1], d[i][j]);
        }
    }
    LL res = 1e18;
    for (int j = 1; j <= n; j++) res = min(res, d[n][j]);
    printf("%I64d\n", res);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值