bzoj2792 [Poi2012]Well

本文解析了POI2012竞赛中Well问题的算法思路及实现细节,通过二分查找和双向更新策略,实现了在给定操作次数内使序列某元素为0并最小化相邻元素差的最大值。

[Poi2012]Well

Time Limit: 40 Sec Memory Limit: 64 MB

Description

给出n个正整数X1,X2,...Xn,可以进行不超过m次操作,每次操作选择一个非零的Xi,并将它减一。

最终要求存在某个k满足Xk=0,并且z=max{|Xi - Xi+1|}最小。
输出最小的z和此时最小的k。

Input

第一行两个正整数n, m (1<=n<=1,000,000, 1<=m<=10^18)。第二行n个正整数X1,X2,...Xn (Xi<=10^9)。

Output

输出k和z。数据保证方案一定存在。

Sample Input

16 15

8 7 6 5 5 5 5 5 6 6 7 8 9 7 5 5

Sample Output

1 2

HINT

将X序列变为

0 2 4 5 5 5 5 5 6 6 7 8 9 7 5 5

此时k=1,z=2,共操作了8+5+2=15次。

大概就是你需要二分一下,然后正反两边操作一遍先让他朴素的满足一下条件。(一定要正反更新啊,不然你只更新一遍的话有可能你改后面的数的时候前面的就又不满足了。)
然后你要找那个0的位置。。。。
暴力。。。。发现需要优化一下。
你大概要把这个操作接近于O(n)。
你可以大概脑补一下,从这个0开始左右两边应该是等差数列的形式, d = t。
你就记一下左边一直到哪里,右边一直到哪里。就用两个指针对吧。。。
你可以想一想,这个指针具有单调性。这点很关键(划重点。。。)
最后算一下,前缀和啥的能优化就优化呗。。。
有点小操作233


#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 6;
int n, k, pos, ans, ini[maxn], a[maxn], let[maxn], rigt[maxn];
long long m, p, ret, qwe, lin, now, s[maxn], pre[maxn];

inline bool check(int t)
{
    ret = 0; bool flag = false; a[0] = 0;
    for(int i = 1; i <= n; ++i) a[i] = ini[i];
    for(int i = 1; i < n; ++i){
        now = abs(a[i] - a[i + 1]);
        if(now <= t) continue;
        now -= t;
        ret += now;
        if(ret > m) return false;
        if(a[i] > a[i + 1]){
            a[i] -= now; continue;
        }       
        a[i + 1] -= now;
    }
    for(int i = n - 1; i >= 1; --i){
        now = abs(a[i] - a[i + 1]);
        if(now <= t) continue;
        now -= t;
        ret += now;
        if(ret > m) return false;
        if(a[i] > a[i + 1]){
            a[i] -= now; continue;
        }       
        a[i + 1] -= now;
    }
    
    s[0] = 0; pre[0] = 0;
    for(int i = 1; i <= n; ++i) s[i] = s[i - 1] + a[i], pre[i] = pre[i - 1] + t;
    
    int p1 = 1, p2 = n;
    for(int i = 1; i <= n; ++i){
        for(;;){
            if(pre[i - p1] <= a[p1] || p1 == i){
                let[i] = p1; break;
            }           
            p1++;
        }
    }
    for(int i = n; i >= 1; --i){
        for(;;){
            if(pre[p2 - i] <= a[p2] || p2 == i){
                rigt[i] = p2; break;
            }
            p2--;
        }
    }
    
    for(int i = 1; i <= n; ++i) pre[i] += pre[i - 1];

    now = 0x7fffffff; pos = 0;
    for(int i = 1; i <= n; ++i){
        lin = a[i];
        if(let[i] != i)
        lin += (s[i - 1] - s[let[i] - 1] - pre[i - let[i]]);
        if(rigt[i] != i)
        lin += (s[rigt[i]] - s[i] - pre[rigt[i] - i]);
        if(lin + ret <= m){
            now = lin; pos = i; return true;
        }
    }
    return false;
}

int main()
{
    scanf("%d%lld", &n, &m);    
    int l = 0, r = 1000000000;
    for(int i = 1; i <= n; ++i) scanf("%d", &ini[i]);
    while(l < r){
        int mid = (l + r) / 2;
        if(!check(mid)){
            l = mid + 1;
        }
        else r = mid;
    }
    check(r);
    printf("%d %d", pos, r);
    
    return 0;
}

转载于:https://www.cnblogs.com/LLppdd/p/9011167.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值