D. Dog Walking(枚举,贪心)

本文介绍了一道关于数组行走的算法题目,你在0点出发,根据给定的数组和一个整数k,可以将遇到的0位置改为[-k, k]内的任意数。目标是返回在回到0点的条件下,最多能走过多少个不同的位置。文章分析了如何通过枚举和贪心策略来找到最优解,并提供了详细的代码实现。

D. Dog Walking

[Link](Problem - D - Codeforces)

题意

​ 你一开始在 0 0 0,给你一个数组 a a a k k k a i a_i ai表示你从所在地往左或右走多少,若 a i = 0 a_i=0 ai=0你可以指定这个位置是 [ − k , k ] [-k,k] [k,k]中的一个数,问你最后走回 0 0 0的条件下最多走多少个不同的点。

思路

  • 枚举,贪心

​ 是否成立在于我们是否能用有的 0 0 0将最终位置挪回到 0 0 0点,对于第 i i i 0 0 0影响的只有 i i i后面的点

​ 最多走多少个不同的点等价于求通过最优操作后整个行进路线最左端 L L L和最右端 R R R的距离加 1 1 1。但是由于 0 0 0的位置不确定我们很难直接确定怎么操作是最优的,最终是哪两个点是 L L L R R R,因此考虑暴力枚举任意两个点,将这两个点之间的长度最长化,由于最终答案一定是最优化后的两个点之间的长度,因此我枚举的答案一定能包含我们的最优解

​ 对于每个 0 0 0我们可以加 [ − k , k ] [-k,k] [k,k],对于我们最长化这个两点之间区间是不方便的,因此考虑将所有的 0 0 0变成 − k -k k,这样等价于我们可以给每个 0 0 0 [ 0 , 2 × k ] [0,2\times k] [0,2×k]之间的数了,这样就是单调的了,方便讨论,设 s i : 是 a i 的 前 缀 和 即 当 前 在 哪 个 位 置 s_i:是a_i的前缀和即当前在哪个位置 si:ai c n t i : 表 前 i 个 数 里 有 多 少 个 1 cnt_i:表前i个数里有多少个1 cnti:i1,则对于任意一个点对 ( i , j ) (i,j) (i,j)分情况来看:

  • s i ≤ s j s_i \le s_j sisj,由于想让长度尽量长,因此希望两点之间的 0 0 0尽量变成大的数,假设最多可以移动 v = ( c n t j − c n t i − 1 ) × 2 k v=(cnt_j-cnt_{i-1})\times2 k v=(cntjcnti1)×2k,由于转化以后的问题所有操作只能为正的,因此最终 s n < 0 s_n<0 sn<0,所以我们最多移动 R = − s n R=-s_n R=sn,多了就没法移回来了,最少移动 L = − s n − ( c n t j − c n t i − 1 ) × 2 k L=-s_n-(cnt_j-cnt_{i-1})\times 2k L=sn(cntjcnti1)×2k,表示除这个区间外是否能将 s n s_n sn抵消,不行就要这个区间内抵消一部分,则我们的最优长度就是 s j − s i − 1 + 1 + m a x ( R , m i n ( L , v ) ) s_j-s_{i-1}+1+max(R,min(L,v)) sjsi1+1+max(R,min(L,v))
  • s i ≥ s j s_i\ge s_j sisj,表示在图上就是 j < − i j<-i j<i,如果我们在区间内进行操作就会让 j j j去向右迫近 i i i因此我们要尽量少的进行操作,因此最优长度就是 s j − s i − 1 + 1 − m i n ( L , 0 ) s_j-s_{i-1}+1-min(L,0) sjsi1+1min(L,0)

暴力枚举每一个点对,取最大即可

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
LL n, m, k;
LL a[N];
int cnt[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> k;
   
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        if (!a[i]) a[i] = -k, cnt[i] = 1;
        a[i] += a[i - 1];
        cnt[i] += cnt[i - 1];
    }

    k *= 2;
    if (a[n] > 0 || -a[n] > (LL)cnt[n] * k) {
        cout << -1 << '\n';
        return 0;
    }
   
    LL res = 0;
    for (int i = 0; i <= n; i ++)
        for (int j = i; j <= n; j ++) {
            LL l = -a[n] - (cnt[n] - (cnt[j] - cnt[i])) * k,
               r = -a[n]; 
            LL v = (cnt[j] - cnt[i]) * k;
            res = max(res, a[j] - a[i] + 1 + min(r, max(l, v)));
            res = max(res, a[i] - a[j] + 1 - max(0ll, l));
        }
    
    cout << res << '\n';
    return 0;
}
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值