李超线段树

李超线段树可以维护两两间至多有一个交点的函数覆盖,单点求极值问题。

codechef NOV17 POLY

给定n个形如yi(x)=$a0+a1^x+a2x^2+a3x^3$的函数以及q个询问.每个询问给定整数t,你需要求出使得yi(t)最小化的函数yi。

Lemma: Polynomial $y=x^3+ax^2+bx+c$ has at most one root greater than $k=\sqrt{\max(|b|,|c|)}+2$.

Proof: Let $u\geq v >k\geq w$ to be the roots of $y$. Then $y=(x-u)(x-v)(x-w)$ so $b=uv+uw+vw$, $c=-uvw$. Since $u,v>\sqrt {|c|}$ it holds that $|w|<1$. Thus $b=uv+w(u+v)> uv-(u+v)=(u-1)(v-1)-1$. But since $u,v>\sqrt {|b|} + 2$ we have $b> (\sqrt {|b|} + 1)^2-1=|b|+2\sqrt {|b|}>b$ which is contradiction.

 于是在t>=350的情况下,三次函数最多有一个交,用李超线段树维护即可

#include <bits/stdc++.h>
#define LL long long 
using namespace std;

  int cnt,n,Q;
  LL f[100001][4],res[1001];

  LL calc(LL tar,int po){
      return(f[po][0]+f[po][1]*tar+f[po][2]*tar*tar+f[po][3]*tar*tar*tar);
  }
 
  struct treenode{
      int lc,rc,l,r,lab;
  }tr[3000001];

  void build(int l,int r){
      tr[++cnt].l=l;tr[cnt].r=r;tr[cnt].lab=1;
      if (l==r) return;
      
      int mid=(l+r)>>1,t=cnt;
      tr[t].lc=cnt+1;
      build(l,mid);
      tr[t].rc=cnt+1;
      build(mid+1,r);
  }

  void ins(int po,int num){
      if (tr[po].l==tr[po].r){
        if (calc(tr[po].l,num)<calc(tr[po].l,tr[po].lab))    
          tr[po].lab=num;
        return;
    }
    
    int mid=(tr[po].l+tr[po].r)>>1,l=tr[po].l,r=tr[po].r;
    if (calc(mid,num)<calc(mid,tr[po].lab)){
      int t=tr[po].lab;tr[po].lab=num;
      if (calc(l,num)>calc(l,t))    
        ins(tr[po].lc,t);
      if (calc(r,num)>calc(r,t))
        ins(tr[po].rc,t);
    }else{
      if (calc(l,num)<calc(l,tr[po].lab))    
        ins(tr[po].lc,num);
      if (calc(r,num)<calc(r,tr[po].lab))
        ins(tr[po].rc,num);
    }
  }

  LL que(int po,int tar){
      if (tr[po].l==tr[po].r)
        return(calc(tr[po].l,tr[po].lab));
      
      LL ret=calc(tar,tr[po].lab);
      int mid=(tr[po].l+tr[po].r)>>1;
      if (tar<=mid)
        ret=min(ret,que(tr[po].lc,tar));else
        ret=min(ret,que(tr[po].rc,tar));
      return(ret);
  } 

  int main(){      
      int T;
      scanf("%d",&T);
      while (T--){
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        scanf("%lld%lld%lld%lld",&f[i][0],&f[i][1],&f[i][2],&f[i][3]);
      for (int i=1;i<=350;i++){
          res[i]=calc(i,1);
          for (int j=2;j<=n;j++)
            res[i]=min(res[i],calc(i,j));
      }
      
      for (int i=1;i<=cnt;i++) tr[i].lc=tr[i].rc=tr[i].lab=0;
      cnt=0;
      build(351,100000);
      for (int i=2;i<=n;i++)
        ins(1,i);
      scanf("%d",&Q);
      while (Q--){
          int t;
          scanf("%d",&t);
          if (t<=350) printf("%lld\n",res[t]);else
            printf("%lld\n",que(1,t));
      }
    }
  }

-------------------------------------------------------------------------

codechef MAY17 KILLER

观察发现贡献的式子是关于dx的二次函数,但是两个二次函数做差所得的函数的二次系数与一次系数符号相同。于是两个函数最多只有一个正的根,可以用李超线段树维护。

在树形的情况下dp转移的常数项会包含一条链上与儿子的dp值的和。

可以发现在合并时相当于将每个儿子的函数值加上一个常数,可以启发式合并。

复杂度$\mathcal{O}(n*log^2{}{n})$

posted @ 2018-02-13 16:32 z1j1n1 阅读(...) 评论(...) 编辑 收藏