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

本文探讨了一种利用双端队列维护决策点的单调性优化算法,通过前后双向扫描,解决了特定数学问题中寻找最优决策点的问题。算法通过维护一个三元组的队列,实时更新决策点的有效区间,结合二分查找确定决策转折点,实现了高效的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

qwq
自闭,啥都不会。

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

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

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

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

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

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

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

考虑双端队列。

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

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

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

考虑入队的过程。

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

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

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

那么我们通过二分来求解

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;
	  }
	 }

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

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

#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、付费专栏及课程。

余额充值