【POI2011】Lightning Conductor/【JSOI2016】灯塔(决策单调性优化dp)

首先进行变形:
a j ≤ a i + p − ∣ i − j ∣ p ≥ max ⁡ j = 1 n ( a j + ∣ i − j ∣ ) − a i \begin{aligned} a_j&\leq a_i+p-\sqrt{|i-j|}\\ p&\geq \max_{j=1}^n\left(a_j+\sqrt{|i-j|}\right)-a_i \end{aligned} ajpai+pij j=1maxn(aj+ij )ai
∣ i − j ∣ |i-j| ij 拆为 max ⁡ ( i − j , j − i ) \max(i-j,j-i) max(ij,ji)
p ≥ max ⁡ ( max ⁡ j ( a j + i − j ) , max ⁡ j ( a j + j − i ) ) − a i p\geq \max\left(\max_j\left(a_j+\sqrt{i-j}\right),\max_j\left(a_j+\sqrt{j-i}\right)\right)-a_i pmax(jmax(aj+ij ),jmax(aj+ji ))ai
两部分做法是一样的,这里只说第一部分。

如果设函数 f j ( x ) = x − j + a j f_j(x)=\sqrt{x-j}+a_j fj(x)=xj +aj,那么我们就是要求这些函数在每个 x = i x=i x=i 的最大值。

盗一张 Flash_Hu 巨佬的图:

显然一个函数至多会对应一个区间的最大值,而且对应着最大值的函数肯定越往后编号越大(因为导致最大值的函数改变的原因只可能是加入了一个编号更大的函数)。

那么我们从编号小到编号大加入这些函数并且用栈维护对应着最大值的函数。

新加入一个函数时,如果它能超过当前栈顶的函数,那么就求交。注意求得的交点也可能在栈顶的函数所对应的最大值区间的左边,这时需要弹出栈顶,然后和新的栈顶再进行比较。比如下图这种情况:

在这里插入图片描述

我们先加入了函数 f f f h h h,再加入函数 g g g 时,发现 g g g 与栈顶 h h h 的交点在 h h h 对应的最大值区域的左边,那么就需要弹出 h h h,重新让 f f f g g g 比较。

代码如下:

#include<bits/stdc++.h>

#define N 500010

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct data
{
	int p,l,r;
	data(){};
	data(int a,int b,int c){p=a,l=b,r=c;}
}q[N];

int n,a[N];
int maxid[N];
int tail;

double gety(int i,int x)
{
	return sqrt(abs(x-i))+1.0*a[i];
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	q[++tail]=data(1,1,n);
	for(int i=2;i<=n;i++)
	{
		if(a[i]<=a[q[tail].p]) continue;
		if(gety(i,n)>gety(q[tail].p,n))
		{
			int l=i,r=n,ans=-1;
			while(l<=r)
			{
				int mid=(l+r)>>1;
				if(gety(i,mid)>gety(q[tail].p,mid)) ans=mid,r=mid-1;
				else l=mid+1;
			}
			if(ans<=q[tail].l)
			{
				tail--;
				i--;
				continue;
			}
			q[tail].r=ans-1;
			q[++tail]=data(i,ans,n);
		}
	}
	for(int i=n;i>=1;i--)
	{
		if(i<q[tail].l) tail--;
		maxid[i]=q[tail].p;
	}
	q[++tail]=data(n,1,n);
	for(int i=n-1;i>=1;i--)
	{
		if(a[i]<=a[q[tail].p]) continue;
		if(gety(i,1)>gety(q[tail].p,1))
		{
			int l=1,r=i,ans=-1;
			while(l<=r)
			{
				int mid=(l+r)>>1;
				if(gety(i,mid)>gety(q[tail].p,mid)) ans=mid,l=mid+1;
				else r=mid-1;
			}
			if(ans>=q[tail].r)
			{
				tail--;
				i++;
				continue;
			}
			q[tail].l=ans+1;
			q[++tail]=data(i,1,ans);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(i>q[tail].r) tail--;
		double x=max(gety(q[tail].p,i),gety(maxid[i],i));
		printf("%d\n",max(0,(int)ceil(x-a[i])));
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值