决策单调性学习笔记 + spoj9070 LIGHTIN - Lightning Conductor (决策单调性+双端队列)

qwq
自闭,啥都不会。

首先我们不难发现题目中的要求可以转化成
p ≥ m a x ( a j + ( i − j ) ) − a i p \ge max(a_j+\sqrt {(i-j)}) - a_i pmax(aj+(ij) )ai

那么这个东西怎么做呢?
qwq
貌似同时考虑两个方向的贡献,并不是特别好处理,那不妨我们从前做一遍,从后做一遍,每次只考虑前面的点对于后面的点的贡献,然后两次直接取 m a x max max o k ok ok

那么这个时候,我们考虑
如果存在一个 k > j k>j k>j
满足 i > k i>k i>k

且存在 a [ k ] + ( i − k ) > a [ j ] + i − j a[k]+\sqrt{(i-k)} >a[j]+\sqrt{i-j} a[k]+(ik) >a[j]+ij 的话

由于我们发现 y = x y=\sqrt x y=x 这个函数是一个上凸的,也就是说,这个函数的斜率是不断变小的,那么由于k比较大, i − k \sqrt{i-k} ik 的变化率比较大,所以对于 i i i,之后的任意一个位置,都应该满足 a [ k ] + ( i − k ) > a [ j ] + i − j a[k]+\sqrt{(i-k)} >a[j]+\sqrt{i-j} a[k]+(ik) >a[j]+ij

所以如果存在这样一个关系,那么我们会发现,从前开始的每一个位置都一个对应的以他作为决策点的区间。
qwq
只要维护好这个,然后用每个点对应的决策点去更新,两个方向取 m a x max max,就解决这个问题了。

但是我们应该怎么维护呢?

考虑双端队列。

维护一个三元组 [ l , r , n u m ] [l,r,num] [l,r,num]
表示这个决策点的编号,和他对应的区间。

首先在第 i i i个元素入队之前,先将 q [ h e a d ] . l + + q[head].l++ q[head].l++,表示每次随着当前点的右移,转移到对应当前点的决策点。

然后弹出不合法的区间,类似 q [ h e a d ] . l > q [ h e a d ] . r q[head].l>q[head].r q[head].l>q[head].r

考虑入队的过程。

如果对于 q [ t a i l ] . l q[tail].l q[tail].l来说,当前的 i i i,比 q [ t a i l ] . n u m q[tail].num q[tail].num更优秀的话,根据上面推的结论,这个队尾元素是没有用的,就可以直接退队,然后把当前元素对应的 l l l更新成 q [ t a i l ] . l q[tail].l q[tail].l,重复这个过程

 while (head<=tail && count(q[tail].l,i)>=count(q[tail].l,q[tail].num)) 
   l=q[tail].l,tail--; //如果大于,后面就一直大于

如果对于 q [ t a i l ] . l q[tail].l q[tail].l,当前的 i i i已经不够优秀了,那我们考虑,对于这个队尾元素维护的 [ l , r ] [l,r] [l,r]区间内,一定存在某一个的点,满足这个点之前是队尾元素更优秀,之后是 i i i更优秀。

那么我们通过二分来求解

int solve(int p,int x,int y)
{
   int l = p,r=n;
   int ans=n+1;
   while (l<=r)
   {
       int mid = l+r >> 1;
       if (count(mid,x)>=count(mid,y)) ans = mid,r=mid-1;
       else l=mid+1;;
   }
   return ans;
}

if (head>tail) //找到决策点 
{
	q[++tail].l=l;
	q[tail].r=n;
	q[tail].num=i;
}
else
{
	 int tmp = solve(q[tail].l,i,q[tail].num);//二分那个转折点 
	 if (tmp!=n+1)
	 {
	    q[tail].r=tmp-1;
		q[++tail].l=tmp;
		q[tail].r=n;
		q[tail].num=i;
	  }
	 }

但是需要注意的是,如果不存在这样一个点,就可以直接忽略当前元素了

经过这一番操作之后,直接用当前位置对应的决策点更新一下,正反做两遍,取 m a x max max o k ok ok

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

const int maxn = 1e6+1e2;

struct Node{
	int l,r,num;
}; 

Node q[maxn];
int head=1,tail=0;
int a[maxn];
int n,m;
double ans[maxn];
double ans1[maxn];
int b[maxn];

double count(int i,int j)
{
	double  xx = sqrt(abs(i-j));
	return 1.0*a[j]+xx;
}

int solve(int p,int x,int y)
{
   int l = p,r=n;
   int ans=n+1;
   while (l<=r)
   {
       int mid = l+r >> 1;
       if (count(mid,x)>=count(mid,y)) ans = mid,r=mid-1;
       else l=mid+1;;
   }
   return ans;
}

int main()
{
  n=read();
  for (int i=1;i<=n;i++) a[i]=read();
  for (int i=1;i<=n;i++) 
  {
  	 q[head].l++;//每次随着当前点的右移,转移到对应当前点的决策点。 
  	 while (head<=tail && q[head].l>q[head].r) head++;
  	 int l=1;
  	 while (head<=tail && count(q[tail].l,i)>=count(q[tail].l,q[tail].num)) l=q[tail].l,tail--; //如果大于,后面就一直大于 
	 if (head>tail) //找到决策点 
	 {
	 	q[++tail].l=l;
	 	q[tail].r=n;
	 	q[tail].num=i;
	 }
	 else
	 {
	 	int tmp = solve(q[tail].l,i,q[tail].num);
		q[tail].r=tmp-1;
		q[++tail].l=tmp;
		q[tail].r=n;
		q[tail].num=i;
	 }
	 ans[i]=count(i,q[head].num)-a[i];
  }
  
  head=1,tail=0;
  memset(q,0,sizeof(q));
  for (int i=1;i<=n;i++) b[i]=a[i];
  for (int i=1;i<=n;i++) a[i]=b[n-i+1];
  
  for (int i=1;i<=n;i++) 
  {
  	 q[head].l++;//每次随着当前点的右移,转移到对应当前点的决策点。 
  	 while (head<=tail && q[head].l>q[head].r) head++;
  	 int l=1;
  	 while (head<=tail && count(q[tail].l,i)>=count(q[tail].l,q[tail].num)) l=q[tail].l,tail--; //如果大于,后面就一直大于 
	 if (head>tail) //找到决策点 
	 {
	 	q[++tail].l=l;
	 	q[tail].r=n;
	 	q[tail].num=i;
	 }
	 else
	 {
	 	int tmp = solve(q[tail].l,i,q[tail].num);//二分那个转折点 
	 	if (tmp!=n+1)
	 	{
		  q[tail].r=tmp-1;
		  q[++tail].l=tmp;
		  q[tail].r=n;
		  q[tail].num=i;
	    }
	 }
	 ans1[i]=count(i,q[head].num)-a[i];
  }
  for (int i=1;i<=n;i++) cout<<(int)ceil(max(ans[i],ans1[n-i+1]))<<"\n";
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值