李超线段树
李超线段树可以维护两两间至多有一个交点的函数覆盖,单点求极值问题。
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})$