bzoj 2216 [Poi2011]Lightning Conductor——单调队列+二分处理决策单调性

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2216

那个关于位置的代价是带根号的,所以随着距离的增加而增长变慢;所以靠后的位置一旦比靠前的位置优,就会一直更优(因为距离相同地增长,基数大的增长慢),所以有决策单调性。

一开始写了和 bzoj 4709 一样的实现,就是使得队列里相邻两个位置的 “后一个位置优于前一个位置的时间” 是单调递增的。但是却 TLE 。不知道为什么。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define db double
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
int g[20];
void wrt(int x)
{
  if(!x){puts("0");return;}int t=0;
  while(x)g[++t]=x%10,x/=10;
  for(int i=t;i;i--)putchar(g[i]+'0');puts("");
}
int Mx(int a,int b){return a>b?a:b;}
const int N=5e5+5;
int n,a[N],q[N],he,tl,ans[N];
db cal(int k,int bh){ return a[k]+sqrt((bh-k));}
int cz(int u,int v)
{
  int l=u,r=n,ret=n+1;
  while(l<=r)
    {
      int mid=l+r>>1;
      if(cal(u,mid)>=cal(v,mid))ret=mid,r=mid-1;
      else l=mid+1;
    }
  return ret;
}
int main()
{
  n=rdn();
  for(int i=1;i<=n;i++)a[i]=rdn();
  for(int i=1;i<=n;i++)
    {
      while(tl-he>=2&&cz(q[tl],q[tl-1])>=cz(i,q[tl]))
    tl--;
      q[++tl]=i;
      while(tl-he>=2&&cz(q[he+2],q[he+1])<=i)he++;
      ans[i]=ceil(cal(q[he+1],i));
    }
  he=tl=0; reverse(a+1,a+n+1);
  for(int i=1;i<=n;i++)
    {
      while(tl-he>=2&&cz(q[tl],q[tl-1])>=cz(i,q[tl]))
    tl--;
      q[++tl]=i;
      while(tl-he>=2&&cz(q[he+2],q[he+1])<=i)he++;
      ans[n-i+1]=Mx(ans[n-i+1],ceil(cal(q[he+1],i)));
    }
  //for(int i=1;i<=n;i++)printf("%d\n",Mx(0,ans[i]-a[n-i+1]));
  for(int i=1;i<=n;i++)wrt(ans[i]-a[n-i+1]);
  return 0;
}
View Code

然后看题解,思路是维护每个决策点能转移给哪段区间。

把之前每个决策点控制的区间放进队列里,每次看看当前决策点 i 能把队尾的哪些区间弹掉(即自己覆盖那些区间,注意自己覆盖的区间应该在自己后面);先判断整个弹掉一个区间,就是看看在区间左端点是不是自己比那个决策点更优(左端点应该在 i 点后面);再在不能整个弹掉的时候判断 i 点能从区间的哪个位置开始往后控制,二分一下即可。

注意算代价应该用 double ,最后再 ceil 。因为过程中 ceil 的话,那个函数图象就不是很好,比如,在二分的时候如果写了 <= 而不是 < ,又有一次 mid 取在了那个 3 和 4 重合的位置,就可能以为下面那个不优的图象在该位置后面是优于上面的。但即使写成 < 而不是 <= ,也还是会 WA , 一定要过程中用 double 才行。

注意把 i 作为决策点插入之后判断一下队首是不是 r > i ,因为插入的时侯可能改了队尾的 r ,即可能改了队首的 r ,所以要在插入之后判断。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define db double
using namespace std;
int rdn()
{
  int ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
db Mx(db a,db b){return a>b?a:b;}
const int N=5e5+5;
int n,a[N],he,tl; bool fg; db ans[N];
struct Node{
  int l,r,p;
  Node(int l=0,int r=0,int p=0):l(l),r(r),p(p) {}
}q[N];
db cal(int p,int k){ return a[p]+sqrt(k-p);}
void solve()
{
  he=tl=0;
  for(int i=1;i<=n;i++)
    {
      if(he==tl||cal(q[tl].p,n)<cal(i,n))
    {
      while(he<tl&&q[tl].l>=i&&cal(q[tl].p,q[tl].l)<=cal(i,q[tl].l))
        tl--;//q[tl].l>=i
      if(he<tl)
        {
          int l=Mx(i,q[tl].l),r=q[tl].r,ret=q[tl].r+1;
          //Mx(i,...) //q[tl].r+1
          while(l<=r)
        {
          int mid=l+r>>1;
          if(cal(q[tl].p,mid)<cal(i,mid))ret=mid,r=mid-1;
          else l=mid+1;
        }
          q[tl].r=ret-1; if(ret<=n)q[++tl]=Node(ret,n,i);//if
        }
      else q[++tl]=Node(i,n,i);
    }
      while(he<tl&&q[he+1].r<i)he++;//after!! for r change
      ans[i]=Mx(ans[i],cal(q[he+1].p,i));
    }
}
int main()
{
  n=rdn();
  for(int i=1;i<=n;i++)a[i]=rdn();
  solve();
  reverse(a+1,a+n+1); reverse(ans+1,ans+n+1); solve();//rev(ans)!
  for(int i=n;i;i--)printf("%d\n",(int)ceil(ans[i])-a[i]);
  return 0;
}

 

posted on 2019-04-02 22:24  Narh  阅读(217)  评论(0编辑  收藏  举报

导航