P8476 「GLR-R3」惊蛰
好厉害的题。去年打比赛拿了 60 暴力,今年考古补了。
首先有结论 \(\forall i\in[1,n],\exists b_i=a_j\),可以类似归纳法的方式证明。
证明:对于 \(i=1\),若 \(b_1\geq a_1\),则令 \(b_1\) 为最大的 \(a_j\) 最优。
若 \(b_1<a_1\),则令 \(b_1\) 为小于 \(a_1\) 的最大的 \(a_j\) 最优。因为 \(b_1\) 已经取到了它能取到的有意义的最大值,\(b_1\) 再变大也不会令之后的决策集合扩大。对于 \(i\not=1\),若 \(b_{i-1}\) 满足上述结论,则当 \(b_i\geq a_i\) 时令 \(b_i=b_{i-1}\) 最优。
若 \(b_i<a_i\),仍然令 \(b_i\) 为小于 \(a_1\) 的最大的 \(a_j\) 最优,证明方式同上。
有了这个结论,我们可以将其离散化,记 \(val_v\) 是 \(v\) 离散化前的值,这样 \(f_{i,j}\) 表示第 \(i\) 个位置的值为 \(num_j\) 时的最小代价,转移方程是:
暴力转移是 \(\mathcal O(n^3)\),发现转移区间是一个后缀,直接设 \(f_{i,j}\) 为后缀 \(\min\),把 \(i\) 滚掉,方程变为:
做完这个后在扫一遍更新后缀 \(\min\),可以做到 \(\mathcal O(n^2)\)。
继续观察转移方程,对于 \(j<a_i\) 的情况是平凡的,就是一个区间加,对于 \(j\geq a_i\),可以把 \(-val_{a_i}\) 拆出来,也是一个区间加(减),考虑 \(val_j\) 如何处理。难道是 KTT
\(val\) 和 \(f\) 都有一个重要的性质:非严格单增。一个区间的 \(f\) 的最做单的位置一定是最小值,这样对于一个区间加了一个 \(val\) 之后,最小值仍然是最左端的位置。
对于最后后缀 \(\min\) 的更新,\(f\) 数组下标 \(<a_i\) 的部分单增,后半部分也是单增,所以可以在左半部分找出一个分界点,分界点左边的值不需要更新,右边的部分直接区间推平即可。
最优发现这些操作线段树大部分都是好做的。对于第二个操作,发现标记是满足结合律的,并且打了标记之后可以快速知道这个区间的答案,所以这个线段树也可以维护。
复杂度 \(\mathcal O(n\log n)\)。
int n,C,len,ans=INF,numa[1000001],a[1000001];
namespace Segment
{
struct{int l,r,minn,tag1,tag2,tag3;}t[10000001];
inline void update(int p){t[p].minn=min(t[p*2].minn,t[p*2+1].minn);}
inline void down1(int p,int v){t[p].minn=v,t[p].tag2=t[p].tag3=0,t[p].tag1=v;}
inline void down2(int p,int v){t[p].minn+=v,t[p].tag2+=v;}
inline void down3(int p,int v){t[p].minn+=v*numa[t[p].l],t[p].tag3+=v;}
inline void spread(int p)
{
if(t[p].tag1!=-1)down1(p*2,t[p].tag1),down1(p*2+1,t[p].tag1),t[p].tag1=-1;
if(t[p].tag2)down2(p*2,t[p].tag2),down2(p*2+1,t[p].tag2),t[p].tag2=0;
if(t[p].tag3)down3(p*2,t[p].tag3),down3(p*2+1,t[p].tag3),t[p].tag3=0;
}
void build(int p,int l,int r)
{
t[p].l=l,t[p].r=r,t[p].tag1=-1;
if(l==r)return;
int mid=l+((r-l)>>1);
build(p*2,l,mid),build(p*2+1,mid+1,r),update(p);
}
int ask(int p,int x)
{
if(t[p].l==t[p].r)return t[p].minn;
return spread(p),x<=t[p*2].r?ask(p*2,x):ask(p*2+1,x);
}
void change(int p,int l,int r,int k)
{
if(l<=t[p].l&&r>=t[p].r)return down1(p,k);
spread(p);
if(l<=t[p*2].r)change(p*2,l,r,k);
if(r>t[p*2].r)change(p*2+1,l,r,k);
update(p);
}
void modify(int p,int l,int r,int k)
{
if(l<=t[p].l&&r>=t[p].r)return t[p].minn+=k,t[p].tag2+=k,void();
spread(p);
if(l<=t[p*2].r)modify(p*2,l,r,k);
if(r>t[p*2].r)modify(p*2+1,l,r,k);
update(p);
}
void add(int p,int l,int r)
{
if(l<=t[p].l&&r>=t[p].r)return down3(p,1);
spread(p);
if(l<=t[p*2].r)add(p*2,l,r);
if(r>t[p*2].r)add(p*2+1,l,r);
update(p);
}
int L;
void check(int p,int val)
{
if(t[p].l==t[p].r)
{
if(t[p].minn>=val)L=min(L,t[p].l);
return;
}
spread(p);
if(t[p*2+1].minn>val)L=min(L,t[p*2+1].l),check(p*2,val);
else check(p*2+1,val);
}
void solve(int p,int k,int val)
{
if(t[p].l==t[p].r)
{
if(t[p].minn>val)L=min(L,t[p].l);
return;
}
spread(p);
if(k>t[p*2].r)
{
if(t[p*2+1].minn>val)check(p*2,val);
solve(p*2+1,k,val);
}
else
solve(p*2,k,val);
}
void print(int p)
{
if(t[p].l==t[p].r)return write(t[p].minn);
spread(p),print(p*2),print(p*2+1);
}
}
using namespace Segment;
inline void mian()
{
read(n,C);int x;
for(int i=1;i<=n;++i)read(a[i]),numa[i]=a[i];
sort(numa+1,numa+1+n),len=unique(numa+1,numa+1+n)-numa-1,build(1,1,len);
for(int i=1;i<=n;++i)
{
a[i]=lower_bound(numa+1,numa+1+len,a[i])-numa;
if(a[i]-1)
{
x=ask(1,a[i]),L=inf;
solve(1,a[i]-1,x-C);
modify(1,1,a[i]-1,C);
L!=inf?change(1,L,a[i]-1,x):void();
}
modify(1,a[i],n,-numa[a[i]]),add(1,a[i],n);
}
write(t[1].minn);
}