题意:
给出一个长度为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;
}