bzoj-2259 新型计算机

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个长度为n的非负序列,将一个元素a修改为A的的代价是|a-A|;

求最小的代价使序列合法 (合法的概念参照原题);

1<=n<=1000000;


题解:

这道题据说要卡O(nlogn),然而我依然选择用O(n*玄学)的算法AC了此题[滑稽];

我们可以很容易的得到一个O(n^2)的算法;

设f[i]为从i开始到序列末尾使序列合法所花费的最小代价,A[i]为i+a[i]+1;

转移即为f[i]=min(f[j]+abs(j-A[i]));

暴力转移是O(n^2),这里我们也可以用线段树优化成O(nlogn);

但是线段树的常数太大了,哪怕是ZKW也会比O(n)慢不知道哪里去了(虽说BZ已经可A了);

我们考虑将转移方程中的abs分情况讨论,那么就是以A[i]为分界的两段方程;

而这两个方程一个可以用单调栈优化,另一个其实可以O(1)出解 (我写的比较凌乱所以都单调栈了= =)

单调栈上可以利用二分找到分界点,比线段树常数要小;

除此以外,实际上我们考虑每一个元素在单调栈中是被谁干掉的,维护这样一个并查集;

这个并查集指向的元素其实就是我们要找的决策点了;

时间复杂度似乎是玄学?不过真的比二分快那么一点。。。


代码:


#include<cctype>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 1000100
#define LEN 1<<16
using namespace std;
typedef long long ll;
ll f[N];
int a[N];
int rt1[N],rt2[N];
int st1[N],st2[N],top1,top2;
char getc()
{
    static char *S,*T,buf[LEN];
    if(S==T)
    {
        T=(S=buf)+fread(buf,1,LEN,stdin);
        if(S==T)
            return EOF;
    }
    return *S++;
}
int read()
{
    static char ch;
    static int D;
    while(!isdigit(ch=getc()));
    for(D=ch-'0';isdigit(ch=getc());)
        D=D*10+ch-'0';
    return D;
}
int find1(int x)
{
    return rt1[x]==x?x:rt1[x]=find1(rt1[x]);
}
int find2(int x)
{
    return rt2[x]==x?x:rt2[x]=find2(rt2[x]);
}
int main()
{
    int n,m,i,j,k,l,r;
    ll A;
    n=read();
    for(i=1;i<=n;i++)
        a[i]=read();
    memset(f,0x3f,sizeof(f));
    f[n+1]=0;
    st1[top1=1]=n+1;
    st2[top2=1]=n+1;
    rt1[n+1]=rt2[n+1]=n+1;
    for(i=n;i>=1;i--)
    {
        rt1[i]=i;
        rt2[i]=i;
        A=(ll)a[i]+i+1;
        if(A<=n+1)
            l=find1(A),r=find2(A);
        else
            l=0,r=st2[1];
        f[i]=min(f[l]+l-A,f[r]-r+A);
        if(f[i]+i<f[st1[top1]]+st1[top1])
            st1[++top1]=i;
        else
            rt1[i]=st1[top1];
        while(top2&&f[i]-i<=f[st2[top2]]-st2[top2])
        {
            rt2[st2[top2]]=i;
            top2--;
        }
        st2[++top2]=i;
    }
    printf("%lld\n",f[1]);
    fclose(stdin);
    fclose(stdout);
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值