zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",(int)ceil(ans[i])-a[i]);
      return 0;
    }
  • 相关阅读:
    sql server 纵横表的转换
    url参数的编码解码Demo
    SqlServer 列的增加和删除
    asp.net下ajax.ajaxMethod使用方法(转)
    js中document.all 的用法
    cookie跨域,跨目录访问及单点登录。
    错误记录:html隐藏域的值存字符串时出错
    .NET下用C#实现邮箱激活功能
    js与C#服务端 json数据交互
    sqlserver数据可空插入报错
  • 原文地址:https://www.cnblogs.com/Narh/p/10645870.html
Copyright © 2011-2022 走看看