bzoj-2259 新型计算机

来源:互联网 发布:足彩分析软件 编辑:程序博客网 时间:2024/05/02 04:45

题意:

给出一个长度为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<<16using 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 0
原创粉丝点击